Files
crmeb-mp/pages/user/components/myApps/index.vue

436 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="my-apps-wrap">
<!-- 展示模式 -->
<template v-if="!editing">
<!-- 顶部:标题 + 编辑按钮 -->
<view class="pt-34 pr-30 pb-20 pl-32 bg--w111-fff rd-t-16rpx mt-20 service ml-20 mr-20">
<view class="flex-row-between">
<view class="fs-30 fw-500 lh-42rpx text--w111-333">我的应用</view>
<view class="edit-btn" @click="enterEdit">
<text class="fs-24 text--w111-1890ff">编辑首页应用</text>
</view>
</view>
</view>
<!-- 应用列表 -->
<view class="pt-10 pr-30 pb-32 pl-32 bg--w111-fff rd-b-16rpx service ml-20 mr-20" v-if="allApps.length">
<view class="grid-column-4 grid-gap-x-76rpx grid-gap-y-54rpx">
<view v-for="(item, index) in allApps" :key="index">
<view class="flex-col flex-center" @click="onAppTap(item)">
<image :src="item.logo" class="app-icon rd-16rpx" v-if="item.logo"></image>
<view class="empty-box" v-else></view>
<text class="fs-26 lh-36rpx text--w111-282828 pt-22">{{ item.name }}</text>
</view>
</view>
</view>
</view>
</template>
<!-- 编辑模式 -->
<template v-else>
<!-- 编辑卡片:整合头栏 + 首页展示栏 + 全部应用 -->
<view class="bg--w111-fff rd-16rpx mt-20 ml-20 mr-20">
<!-- 顶部操作栏 -->
<view class="edit-header">
<view class="edit-header-inner">
<view class="header-btn btn-cancel" @click="cancelEdit">取消</view>
<view class="fs-30 fw-500 text--w111-333">编辑首页展示</view>
<view class="header-btn btn-save" @click="saveEdit">保存</view>
</view>
</view>
<!-- 首页展示栏(横向滚动) -->
<view class="home-bar-wrap" v-if="editSelectedIds.length">
<view class="home-bar-label fs-28 text--w111-333">首页展示栏</view>
<scroll-view class="home-bar-scroll" scroll-x enhanced show-scrollbar="false">
<view class="home-bar-inner">
<view
class="home-bar-item"
v-for="(item, index) in editSelectedList"
:key="item.id"
>
<view class="home-bar-icon-wrap">
<image :src="item.logo" class="home-bar-icon rd-16rpx" v-if="item.logo"></image>
<view class="empty-box-sm" v-else></view>
<!-- 删除标记 -->
<view class="badge badge-remove" @click="removeFromHome(item.id)">✕</view>
</view>
<text class="home-bar-name">{{ item.name }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 全部应用列表 -->
<view class="all-apps-list">
<view class="fs-28 text--w111-333 mb-20">全部应用</view>
<view class="grid-column-4 grid-gap-x-76rpx grid-gap-y-54rpx">
<view v-for="(item, index) in allApps" :key="index">
<view
class="flex-col flex-center pos-relative"
@click="toggleAppSelect(item)"
>
<view class="app-icon-wrap">
<image :src="item.logo" class="app-icon rd-16rpx" v-if="item.logo"></image>
<view class="empty-box" v-else></view>
<!-- 已选遮罩 -->
<view class="selected-overlay" v-if="isSelected(item.id)">
<text class="selected-check">✓</text>
</view>
</view>
<!-- 添加标记(未选中时) -->
<view
class="badge badge-add"
v-if="!isSelected(item.id) && editSelectedIds.length < 6"
@click.stop="addToHome(item)"
>+</view>
<text
class="fs-26 lh-36rpx pt-22"
:class="isSelected(item.id) ? 'text--w111-ccc' : 'text--w111-282828'"
>{{ item.name }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
</view>
</template>
<script>
const STORAGE_KEY = 'MY_HOME_APPS'
const MAX_HOME_APPS = 6
export default {
name: 'userMyApps',
data() {
return {
allApps: [],
// Storage 数据
homeApps: {
showOnHome: true,
appIds: []
},
editing: false,
// 编辑中的临时副本
editAppIds: []
}
},
computed: {
editSelectedIds() {
return this.editAppIds
},
// 编辑模式中已选应用的完整对象列表(按 allApps 顺序排列)
editSelectedList() {
const idSet = new Set(this.editAppIds)
const filtered = this.allApps.filter(item => idSet.has(item.id))
const indexMap = new Map(this.allApps.map((item, i) => [item.id, i]))
filtered.sort((a, b) => (indexMap.get(a.id) ?? 999) - (indexMap.get(b.id) ?? 999))
return filtered
}
},
mounted() {
this.loadData()
this.fetchApps()
},
// 从首页"更多"跳转过来时刷新数据
onShow() {
this.loadData()
},
methods: {
// ---------- 数据层 ----------
loadData() {
try {
const raw = uni.getStorageSync(STORAGE_KEY)
if (raw) {
this.homeApps = raw
}
} catch (e) {
console.error('[myApps] loadData error:', e)
}
},
saveData() {
try {
uni.setStorageSync(STORAGE_KEY, this.homeApps)
} catch (e) {
console.error('[myApps] saveData error:', e)
}
},
fetchApps() {
uni.request({
url: 'https://mp.yizhezhe.com/api/mp/apps',
data: {
isPublished: 1,
pageSize: 50
},
success: (res) => {
if (res.data?.code === 200 && res.data?.result?.items) {
this.allApps = res.data.result.items
} else if (res.data?.status === 200 && res.data?.data) {
this.allApps = res.data.data
} else {
return
}
this.autoFillHomeApps()
},
fail: () => {
this.allApps = []
}
})
},
// 自动填充首页展示应用(与 appCenter 逻辑一致)
autoFillHomeApps() {
const ids = this.homeApps.appIds || []
const currentCount = ids.length
if (currentCount >= 6) return
const idSet = new Set(ids)
const recommended = this.allApps
.filter(item => item.isRecommended && !idSet.has(item.id))
if (!recommended.length) return
const slots = 6 - currentCount
const toAdd = recommended.slice(0, slots).map(item => item.id)
this.homeApps.appIds = [...ids, ...toAdd]
this.saveData()
},
// ---------- 展示模式 ----------
onAppTap(item) {
if (!item.url) return
this.$util.JumpPath(item.url)
},
// ---------- 编辑模式 ----------
enterEdit() {
// 复制当前 appIds 作为编辑副本
this.editAppIds = [...(this.homeApps.appIds || [])]
this.editing = true
},
cancelEdit() {
this.editing = false
this.editAppIds = []
},
saveEdit() {
this.homeApps.appIds = [...this.editAppIds]
this.saveData()
this.editing = false
},
// 判断应用是否已选
isSelected(id) {
return this.editAppIds.includes(id)
},
// 添加到首页栏(从全部应用点击+)
addToHome(item) {
if (this.editAppIds.length >= MAX_HOME_APPS) {
uni.showToast({ title: `最多选择${MAX_HOME_APPS}个应用`, icon: 'none' })
return
}
if (!this.editAppIds.includes(item.id)) {
this.editAppIds.push(item.id)
}
},
// 从首页栏移除(点击✕)
removeFromHome(id) {
this.editAppIds = this.editAppIds.filter(i => i !== id)
},
// 点击应用图标切换选中状态
toggleAppSelect(item) {
if (this.isSelected(item.id)) {
this.removeFromHome(item.id)
} else {
this.addToHome(item)
}
}
}
}
</script>
<style scoped lang="scss">
.my-apps-wrap {
// 左右布局
.flex-row-between {
display: flex;
justify-content: space-between;
align-items: center;
}
// 应用图标(展示模式 + 编辑模式通用)
.app-icon {
width: 96rpx;
height: 96rpx;
}
.empty-box {
width: 96rpx;
height: 96rpx;
background-color: #f3f9ff;
border-radius: 8rpx;
}
.app-icon-wrap {
position: relative;
width: 96rpx;
height: 96rpx;
}
// 编辑按钮
.edit-btn {
display: inline-flex;
align-items: center;
padding: 6rpx 20rpx;
border: 2rpx solid #1890ff;
border-radius: 10rpx;
flex-shrink: 0;
}
// ---------- 编辑模式 ----------
.edit-header {
padding: 24rpx 30rpx 0;
.edit-header-inner {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 56rpx;
padding: 0 28rpx;
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 500;
flex-shrink: 0;
}
.btn-cancel {
color: #666;
background: #f5f5f5;
}
.btn-save {
color: #fff;
background: linear-gradient(135deg, #1890ff, #096dd9);
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.35);
}
}
// 首页展示栏
.home-bar-wrap {
padding: 24rpx 30rpx 0;
margin-top: 20rpx;
.home-bar-label {
margin-bottom: 16rpx;
}
.home-bar-scroll {
white-space: nowrap;
overflow: visible !important;
.home-bar-inner {
display: flex;
gap: 24rpx;
.home-bar-item {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
width: 100rpx;
padding-top: 12rpx;
.home-bar-icon-wrap {
position: relative;
width: 90rpx;
height: 90rpx;
overflow: visible;
.home-bar-icon {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.empty-box-sm {
width: 100%;
height: 100%;
background-color: #f3f9ff;
border-radius: 16rpx;
}
}
.home-bar-name {
font-size: 22rpx;
color: #333;
margin-top: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100rpx;
}
}
}
}
}
// 全部应用列表
.all-apps-list {
padding: 30rpx 30rpx 34rpx;
}
// ---------- 通用徽标 ----------
.badge {
position: absolute;
width: 36rpx;
height: 36rpx;
border-radius: 50%;
font-size: 20rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
z-index: 2;
}
.badge-remove {
top: -8rpx;
right: -8rpx;
background: #ff4d4f;
}
.badge-add {
top: -8rpx;
right: 6rpx;
background: #52c41a;
}
.selected-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.35);
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
.selected-check {
color: #fff;
font-size: 28rpx;
font-weight: bold;
}
}
.pos-relative {
position: relative;
}
}
</style>