feat: 工作台常用入口可批量编辑,简报改为横幅,列表补下拉刷新

各角色工作台支持批量保存常用入口和业务员看板;AI 简报改成单行横幅避免锁死滑动;仓库/提现/对账等列表统一下拉刷新,并补代付码弹窗。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
李琦
2026-08-18 08:53:16 +08:00
parent 97bc48c17c
commit 62ffb6fd31
85 changed files with 2809 additions and 787 deletions

View File

@@ -1,8 +1,8 @@
<template>
<!--
功能中心页共享组件(诊所管理员/平台管理员/业务员/诊所推广员四端「功能」tab 复用)
结构:顶部搜索框 + u-tabs/swiper 联动(常用 | 全部 | 各分组),金刚区宫格展示入口
交互:点击入口跳转并静默上报使用;长按入口收藏/取消收藏到「我的常用」;搜索本地过滤
结构:顶部搜索框 + u-tabs/swiper 联动(全部 | 各分组),金刚区宫格展示入口
常用/最近只在首页,本页不再重复;长按仍可收藏到首页「我的常用」
放主包 components/ 原因:四端页面分属不同分包,微信不允许跨分包引用组件(同 xk-service-card)
-->
<view class="wf-wrap" :style="{ height: wrapHeight }">
@@ -33,7 +33,14 @@
</view>
<!-- 搜索态:覆盖 tabs/swiper,展示扁平匹配结果 -->
<scroll-view v-else-if="isSearching" scroll-y class="wf-search-scroll">
<scroll-view
v-else-if="isSearching"
scroll-y
class="wf-search-scroll"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresherRefresh"
>
<view class="wf-page">
<view class="wf-panel" v-if="searchItems.length">
<view class="wf-grid">
@@ -65,40 +72,15 @@
</view>
<swiper class="wf-swiper" :current="current" @change="onSwiperChange">
<swiper-item v-for="(page, pi) in swiperPages" :key="pi">
<scroll-view scroll-y class="wf-scroll">
<!-- 常用 tab:我的常用 + 最近使用两个分区 -->
<view v-if="page.type === 'fav'" class="wf-page">
<view class="wf-group-title">我的常用</view>
<view class="wf-panel" v-if="favItems.length">
<view class="wf-grid">
<xk-service-card
v-for="item in favItems" :key="item.key"
mode="grid"
:icon="item.icon" :label="item.label" :theme="item.theme"
@click="onItemTap(item)" @longpress="onItemLongpress(item)"
></xk-service-card>
</view>
</view>
<view class="wf-hint" v-else>
<text>长按任意功能,可添加到我的常用</text>
</view>
<view class="wf-group-title">最近使用</view>
<view class="wf-panel" v-if="recentItems.length">
<view class="wf-grid">
<xk-service-card
v-for="item in recentItems" :key="item.key"
mode="grid"
:icon="item.icon" :label="item.label" :theme="item.theme"
@click="onItemTap(item)" @longpress="onItemLongpress(item)"
></xk-service-card>
</view>
</view>
<view class="wf-hint" v-else>
<text>暂无使用记录,点击功能后会出现在这里</text>
</view>
</view>
<scroll-view
scroll-y
class="wf-scroll"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresherRefresh"
>
<!-- 全部 tab:所有分组一页看全(分组标题 + 各组金刚区) -->
<view v-else-if="page.type === 'all'" class="wf-page">
<view v-if="page.type === 'all'" class="wf-page">
<view v-for="section in sections" :key="section.key">
<view class="wf-group-title">{{ section.title }}</view>
<view class="wf-panel">
@@ -130,6 +112,12 @@
</swiper-item>
</swiper>
</block>
<!-- 右下角悬浮:打开全部入口的加减编辑(已加减号 / 未加加号,支持批量) -->
<view v-if="!loading && !failed && sections.length" class="wf-fab" hover-class="wf-fab-hover" @tap="openEditor">
<u-icon name="plus" color="#FFFFFF" size="40"></u-icon>
</view>
<workbench-fav-editor ref="favEditor" :api="api" @saved="onEditorSaved"></workbench-fav-editor>
</view>
</template>
@@ -144,17 +132,20 @@
* - getEntries() GET 当前角色启用入口(扁平数组)
* - getUserData() GET { favorites: [], recents: [] }
* - toggleFavorite(code) POST 收藏/取消收藏,data = { is_favorite }
* - batchSaveFavorites(codes) POST 批量保存常用
* - reportUsage(code) POST 使用上报(静默,失败不影响跳转)
*
* 事件:
* - @special(item):点击了无 path 的特殊入口(如推广员端 cs_transfer 传方弹窗),由壳页按 code 处理
*/
import { groupEntriesToSections, normalizeEntry } from '@/utils/workbenchEntries.js';
import pullRefreshMixin from '@/subPackages/sub_business_shared/common/pullRefreshMixin.js';
export default {
name: 'WorkbenchFeatures',
mixins: [pullRefreshMixin],
props: {
/** 角色接口集合:{ getEntries, getUserData, toggleFavorite, reportUsage } */
/** 角色接口集合:{ getEntries, getUserData, toggleFavorite, batchSaveFavorites, reportUsage } */
api: { type: Object, required: true },
},
data() {
@@ -163,9 +154,8 @@ export default {
failed: false,
/** 分组入口(groupEntriesToSections 输出结构) */
sections: [],
/** 我的常用 / 最近使用(已归一化为渲染项) */
/** 仅用于长按菜单判断是否已收藏,不在本页展示列表(展示在首页) */
favItems: [],
recentItems: [],
keyword: '',
current: 0,
/** 内容区高度(px):layout 的 navbar 与 tabbar 之间,swiper 需要确定高度才能滚动 */
@@ -179,10 +169,9 @@ export default {
isSearching() {
return this.keyword.trim() !== '';
},
/** swiper 页序列:固定「常用」「全部」两页在前,其后每个分组一页 */
/** swiper 页序列:固定「全部」在前,其后每个分组一页(常用/最近只在首页) */
swiperPages() {
const pages = [
{ key: '__fav__', type: 'fav', title: '常用', items: [] },
{ key: '__all__', type: 'all', title: '全部', items: [] },
];
this.sections.forEach((s) => {
@@ -238,34 +227,39 @@ export default {
}
this.bodyHeight = Math.max(400, sys.windowHeight - top - 100 * rpx - safeBottom);
},
/** 全量加载:入口列表(必须)+ 用户数据(失败静默,常用/历史留空) */
/**
* 全量加载入口 + 收藏状态。
* 下拉刷新时不切整页 loading,否则 scroll-view 被卸掉转圈收不起来。
*/
reload() {
this.loading = true;
this.failed = false;
this.api.getEntries().then(({ data, ok }) => {
const silent = this.isRefreshing;
if (!silent) {
this.loading = true;
this.failed = false;
}
const entriesP = this.api.getEntries().then(({ data, ok }) => {
if (ok && Array.isArray(data)) {
this.sections = groupEntriesToSections(data);
// 分组数变化时防止 current 越界(固定页为常用+全部两页,如后台调整了分组)
if (this.current >= this.sections.length + 2) this.current = 0;
} else {
if (this.current >= this.sections.length + 1) this.current = 0;
this.failed = false;
} else if (!silent) {
this.failed = true;
}
this.loading = false;
}).catch(() => {
this.failed = true;
if (!silent) this.failed = true;
}).finally(() => {
this.loading = false;
});
this.refreshUserData();
return Promise.all([entriesP, this.refreshUserData()]);
},
/** 刷新常用/最近使用(壳页 onShow 也会调用,保证跳转返回后历史及时更新) */
/** 刷新收藏状态(长按文案用;列表本身只在首页展示) */
refreshUserData() {
if (!this.api.getUserData) return;
this.api.getUserData().then(({ data, ok }) => {
if (!this.api.getUserData) return Promise.resolve();
return this.api.getUserData().then(({ data, ok }) => {
if (!ok || !data) return;
this.favItems = this.normalizeList(data.favorites);
this.recentItems = this.normalizeList(data.recents);
}).catch(() => {
// 用户数据是增强信息,失败静默(不打断功能页主流程)
// 收藏状态是增强信息,失败静默(不打断功能页主流程)
});
},
/** 后端入口对象数组 → 渲染项数组(与首页同一映射,字段完全一致) */
@@ -318,6 +312,14 @@ export default {
},
});
},
/** 打开全部入口编辑抽屉(悬浮按钮) */
openEditor() {
if (this.$refs.favEditor) this.$refs.favEditor.open();
},
/** 批量保存后刷新收藏状态,长按菜单文案与首页常用保持一致 */
onEditorSaved() {
this.refreshUserData();
},
/** 收藏切换:成功后按后端返回状态提示并刷新常用列表 */
doToggleFavorite(item) {
if (!this.api.toggleFavorite) return;
@@ -340,6 +342,7 @@ export default {
<style lang="scss" scoped>
/* 纵向 flex:搜索/tabs 固定高度,swiper 吃满剩余空间(swiper 必须有确定高度才能滚动) */
.wf-wrap {
position: relative;
display: flex;
flex-direction: column;
background: #f9fafb;
@@ -429,4 +432,22 @@ export default {
.wf-retry-hover {
opacity: 0.8;
}
/* 悬浮添加:贴内容区右下,wrap 已扣掉 tabbar 高度 */
.wf-fab {
position: absolute;
right: 32rpx;
bottom: 32rpx;
width: 104rpx;
height: 104rpx;
border-radius: 52rpx;
background: #6ACDBB;
box-shadow: 0 8rpx 24rpx rgba(106, 205, 187, 0.35);
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
}
.wf-fab-hover {
opacity: 0.85;
}
</style>