2026-08-12 08:57:35 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<!--
|
|
|
|
|
|
功能中心页共享组件(诊所管理员/平台管理员/业务员/诊所推广员四端「功能」tab 复用)
|
2026-08-18 08:53:16 +08:00
|
|
|
|
结构:顶部搜索框 + u-tabs/swiper 联动(全部 | 各分组),金刚区宫格展示入口
|
|
|
|
|
|
常用/最近只在首页,本页不再重复;长按仍可收藏到首页「我的常用」
|
2026-08-12 08:57:35 +08:00
|
|
|
|
放主包 components/ 原因:四端页面分属不同分包,微信不允许跨分包引用组件(同 xk-service-card)
|
|
|
|
|
|
-->
|
|
|
|
|
|
<view class="wf-wrap" :style="{ height: wrapHeight }">
|
|
|
|
|
|
<!-- 搜索框:本地过滤当前角色全部入口,输入即出结果 -->
|
|
|
|
|
|
<view class="wf-search">
|
|
|
|
|
|
<u-search
|
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
|
placeholder="搜索功能"
|
|
|
|
|
|
shape="round"
|
|
|
|
|
|
bg-color="#FFFFFF"
|
|
|
|
|
|
:show-action="false"
|
|
|
|
|
|
:clearabled="true"
|
|
|
|
|
|
></u-search>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 加载/失败/空配置三态:占满内容区居中展示 -->
|
|
|
|
|
|
<view v-if="loading" class="wf-center">
|
|
|
|
|
|
<text class="wf-center-text">加载中...</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="failed" class="wf-center">
|
|
|
|
|
|
<text class="wf-center-text">功能列表加载失败</text>
|
|
|
|
|
|
<view class="wf-retry" hover-class="wf-retry-hover" @tap="reload">
|
|
|
|
|
|
<text>重新加载</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="sections.length === 0" class="wf-center">
|
|
|
|
|
|
<text class="wf-center-text">暂无可用功能,请联系管理员配置</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 搜索态:覆盖 tabs/swiper,展示扁平匹配结果 -->
|
2026-08-18 08:53:16 +08:00
|
|
|
|
<scroll-view
|
|
|
|
|
|
v-else-if="isSearching"
|
|
|
|
|
|
scroll-y
|
|
|
|
|
|
class="wf-search-scroll"
|
|
|
|
|
|
refresher-enabled
|
|
|
|
|
|
:refresher-triggered="isRefreshing"
|
|
|
|
|
|
@refresherrefresh="onRefresherRefresh"
|
|
|
|
|
|
>
|
2026-08-12 08:57:35 +08:00
|
|
|
|
<view class="wf-page">
|
|
|
|
|
|
<view class="wf-panel" v-if="searchItems.length">
|
|
|
|
|
|
<view class="wf-grid">
|
|
|
|
|
|
<xk-service-card
|
|
|
|
|
|
v-for="item in searchItems" :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>没有找到「{{ keyword }}」相关功能</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 常规态:tabs + swiper 双向联动(点 tab / 左右滑都走 switchTab) -->
|
|
|
|
|
|
<block v-else>
|
|
|
|
|
|
<view class="wf-tabs">
|
|
|
|
|
|
<u-tabs
|
|
|
|
|
|
:list="tabs"
|
|
|
|
|
|
:is-scroll="tabs.length > 4"
|
|
|
|
|
|
:current="current"
|
|
|
|
|
|
:height="88"
|
|
|
|
|
|
active-color="#6ACDBB"
|
|
|
|
|
|
@change="switchTab"
|
|
|
|
|
|
></u-tabs>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<swiper class="wf-swiper" :current="current" @change="onSwiperChange">
|
|
|
|
|
|
<swiper-item v-for="(page, pi) in swiperPages" :key="pi">
|
2026-08-18 08:53:16 +08:00
|
|
|
|
<scroll-view
|
|
|
|
|
|
scroll-y
|
|
|
|
|
|
class="wf-scroll"
|
|
|
|
|
|
refresher-enabled
|
|
|
|
|
|
:refresher-triggered="isRefreshing"
|
|
|
|
|
|
@refresherrefresh="onRefresherRefresh"
|
|
|
|
|
|
>
|
2026-08-12 08:57:35 +08:00
|
|
|
|
<!-- 全部 tab:所有分组一页看全(分组标题 + 各组金刚区) -->
|
2026-08-18 08:53:16 +08:00
|
|
|
|
<view v-if="page.type === 'all'" class="wf-page">
|
2026-08-12 08:57:35 +08:00
|
|
|
|
<view v-for="section in sections" :key="section.key">
|
|
|
|
|
|
<view class="wf-group-title">{{ section.title }}</view>
|
|
|
|
|
|
<view class="wf-panel">
|
|
|
|
|
|
<view class="wf-grid">
|
|
|
|
|
|
<xk-service-card
|
|
|
|
|
|
v-for="item in section.items" :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>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 分组 tab:该组全部入口金刚区 -->
|
|
|
|
|
|
<view v-else class="wf-page">
|
|
|
|
|
|
<view class="wf-panel">
|
|
|
|
|
|
<view class="wf-grid">
|
|
|
|
|
|
<xk-service-card
|
|
|
|
|
|
v-for="item in page.items" :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>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
</swiper-item>
|
|
|
|
|
|
</swiper>
|
|
|
|
|
|
</block>
|
2026-08-18 08:53:16 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 右下角悬浮:打开全部入口的加减编辑(已加减号 / 未加加号,支持批量) -->
|
|
|
|
|
|
<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>
|
2026-08-12 08:57:35 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
/**
|
|
|
|
|
|
* workbench-features 功能中心共享组件
|
|
|
|
|
|
*
|
|
|
|
|
|
* 为什么做成组件而不是共享页面:四端「功能」tab 页需要套各自的 page-layout(navbar/tabbar 配置不同),
|
|
|
|
|
|
* 页面壳由各端提供,数据与交互全部收敛到这里,避免四份复制。
|
|
|
|
|
|
*
|
|
|
|
|
|
* props.api 约定(由各端壳页注入对应角色的接口,全部返回 Promise<{ res, data, ok }>):
|
|
|
|
|
|
* - getEntries() GET 当前角色启用入口(扁平数组)
|
|
|
|
|
|
* - getUserData() GET { favorites: [], recents: [] }
|
|
|
|
|
|
* - toggleFavorite(code) POST 收藏/取消收藏,data = { is_favorite }
|
2026-08-18 08:53:16 +08:00
|
|
|
|
* - batchSaveFavorites(codes) POST 批量保存常用
|
2026-08-12 08:57:35 +08:00
|
|
|
|
* - reportUsage(code) POST 使用上报(静默,失败不影响跳转)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 事件:
|
|
|
|
|
|
* - @special(item):点击了无 path 的特殊入口(如推广员端 cs_transfer 传方弹窗),由壳页按 code 处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { groupEntriesToSections, normalizeEntry } from '@/utils/workbenchEntries.js';
|
2026-08-18 08:53:16 +08:00
|
|
|
|
import pullRefreshMixin from '@/subPackages/sub_business_shared/common/pullRefreshMixin.js';
|
2026-08-12 08:57:35 +08:00
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'WorkbenchFeatures',
|
2026-08-18 08:53:16 +08:00
|
|
|
|
mixins: [pullRefreshMixin],
|
2026-08-12 08:57:35 +08:00
|
|
|
|
props: {
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/** 角色接口集合:{ getEntries, getUserData, toggleFavorite, batchSaveFavorites, reportUsage } */
|
2026-08-12 08:57:35 +08:00
|
|
|
|
api: { type: Object, required: true },
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
loading: true,
|
|
|
|
|
|
failed: false,
|
|
|
|
|
|
/** 分组入口(groupEntriesToSections 输出结构) */
|
|
|
|
|
|
sections: [],
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/** 仅用于长按菜单判断是否已收藏,不在本页展示列表(展示在首页) */
|
2026-08-12 08:57:35 +08:00
|
|
|
|
favItems: [],
|
|
|
|
|
|
keyword: '',
|
|
|
|
|
|
current: 0,
|
|
|
|
|
|
/** 内容区高度(px):layout 的 navbar 与 tabbar 之间,swiper 需要确定高度才能滚动 */
|
|
|
|
|
|
bodyHeight: 600,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
wrapHeight() {
|
|
|
|
|
|
return this.bodyHeight + 'px';
|
|
|
|
|
|
},
|
|
|
|
|
|
isSearching() {
|
|
|
|
|
|
return this.keyword.trim() !== '';
|
|
|
|
|
|
},
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/** swiper 页序列:固定「全部」在前,其后每个分组一页(常用/最近只在首页) */
|
2026-08-12 08:57:35 +08:00
|
|
|
|
swiperPages() {
|
|
|
|
|
|
const pages = [
|
|
|
|
|
|
{ key: '__all__', type: 'all', title: '全部', items: [] },
|
|
|
|
|
|
];
|
|
|
|
|
|
this.sections.forEach((s) => {
|
|
|
|
|
|
pages.push({ key: s.key, type: 'group', title: s.title, items: s.items });
|
|
|
|
|
|
});
|
|
|
|
|
|
return pages;
|
|
|
|
|
|
},
|
|
|
|
|
|
/** u-tabs 数据源(name 字段) */
|
|
|
|
|
|
tabs() {
|
|
|
|
|
|
return this.swiperPages.map((p) => ({ name: p.title }));
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 全部入口扁平列表(搜索用) */
|
|
|
|
|
|
allItems() {
|
|
|
|
|
|
const list = [];
|
|
|
|
|
|
this.sections.forEach((s) => {
|
|
|
|
|
|
s.items.forEach((item) => list.push(item));
|
|
|
|
|
|
});
|
|
|
|
|
|
return list;
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 搜索结果:名称/描述包含关键词(大小写不敏感) */
|
|
|
|
|
|
searchItems() {
|
|
|
|
|
|
const kw = this.keyword.trim().toLowerCase();
|
|
|
|
|
|
if (!kw) return [];
|
|
|
|
|
|
return this.allItems.filter((item) => {
|
|
|
|
|
|
const label = (item.label || '').toLowerCase();
|
|
|
|
|
|
const desc = (item.desc || '').toLowerCase();
|
|
|
|
|
|
return label.indexOf(kw) !== -1 || desc.indexOf(kw) !== -1;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 已收藏 code 集合(长按菜单文案判断用) */
|
|
|
|
|
|
favCodes() {
|
|
|
|
|
|
return this.favItems.map((item) => item.key);
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
this.calcHeight();
|
|
|
|
|
|
this.reload();
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算内容区高度:窗口高度 - layout 顶部(状态栏+navbar)- layout 底部 tabbar 预留
|
|
|
|
|
|
* 与各端 page-layout 的 bodyStyle 计算保持一致(paddingTop=状态栏+navbar px,paddingBottom=100rpx+安全区)
|
|
|
|
|
|
*/
|
|
|
|
|
|
calcHeight() {
|
|
|
|
|
|
const sys = uni.getSystemInfoSync();
|
|
|
|
|
|
const rpx = sys.windowWidth / 750;
|
|
|
|
|
|
const top = (this.$statusBarHeight || sys.statusBarHeight || 0) + (this.$navbarHeight || 44);
|
|
|
|
|
|
let safeBottom = 0;
|
|
|
|
|
|
if (sys.safeAreaInsets && sys.safeAreaInsets.bottom) {
|
|
|
|
|
|
safeBottom = sys.safeAreaInsets.bottom;
|
|
|
|
|
|
} else if (sys.safeArea && sys.screenHeight) {
|
|
|
|
|
|
safeBottom = sys.screenHeight - sys.safeArea.bottom;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.bodyHeight = Math.max(400, sys.windowHeight - top - 100 * rpx - safeBottom);
|
|
|
|
|
|
},
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 全量加载入口 + 收藏状态。
|
|
|
|
|
|
* 下拉刷新时不切整页 loading,否则 scroll-view 被卸掉转圈收不起来。
|
|
|
|
|
|
*/
|
2026-08-12 08:57:35 +08:00
|
|
|
|
reload() {
|
2026-08-18 08:53:16 +08:00
|
|
|
|
const silent = this.isRefreshing;
|
|
|
|
|
|
if (!silent) {
|
|
|
|
|
|
this.loading = true;
|
|
|
|
|
|
this.failed = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const entriesP = this.api.getEntries().then(({ data, ok }) => {
|
2026-08-12 08:57:35 +08:00
|
|
|
|
if (ok && Array.isArray(data)) {
|
|
|
|
|
|
this.sections = groupEntriesToSections(data);
|
2026-08-18 08:53:16 +08:00
|
|
|
|
if (this.current >= this.sections.length + 1) this.current = 0;
|
|
|
|
|
|
this.failed = false;
|
|
|
|
|
|
} else if (!silent) {
|
2026-08-12 08:57:35 +08:00
|
|
|
|
this.failed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(() => {
|
2026-08-18 08:53:16 +08:00
|
|
|
|
if (!silent) this.failed = true;
|
|
|
|
|
|
}).finally(() => {
|
2026-08-12 08:57:35 +08:00
|
|
|
|
this.loading = false;
|
|
|
|
|
|
});
|
2026-08-18 08:53:16 +08:00
|
|
|
|
return Promise.all([entriesP, this.refreshUserData()]);
|
2026-08-12 08:57:35 +08:00
|
|
|
|
},
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/** 刷新收藏状态(长按文案用;列表本身只在首页展示) */
|
2026-08-12 08:57:35 +08:00
|
|
|
|
refreshUserData() {
|
2026-08-18 08:53:16 +08:00
|
|
|
|
if (!this.api.getUserData) return Promise.resolve();
|
|
|
|
|
|
return this.api.getUserData().then(({ data, ok }) => {
|
2026-08-12 08:57:35 +08:00
|
|
|
|
if (!ok || !data) return;
|
|
|
|
|
|
this.favItems = this.normalizeList(data.favorites);
|
|
|
|
|
|
}).catch(() => {
|
2026-08-18 08:53:16 +08:00
|
|
|
|
// 收藏状态是增强信息,失败静默(不打断功能页主流程)
|
2026-08-12 08:57:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 后端入口对象数组 → 渲染项数组(与首页同一映射,字段完全一致) */
|
|
|
|
|
|
normalizeList(list) {
|
|
|
|
|
|
if (!Array.isArray(list)) return [];
|
|
|
|
|
|
const items = [];
|
|
|
|
|
|
list.forEach((entry) => {
|
|
|
|
|
|
const item = normalizeEntry(entry);
|
|
|
|
|
|
if (item) items.push(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
return items;
|
|
|
|
|
|
},
|
|
|
|
|
|
/** tabs 与 swiper 统一切换入口 */
|
|
|
|
|
|
switchTab(index) {
|
|
|
|
|
|
if (this.current === index) return;
|
|
|
|
|
|
this.current = index;
|
|
|
|
|
|
},
|
|
|
|
|
|
/** swiper 手势滑动回调:回写 current 让顶部 tab 高亮跟随 */
|
|
|
|
|
|
onSwiperChange(e) {
|
|
|
|
|
|
this.switchTab(e.detail.current);
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 点击入口:静默上报使用 → 跳转
|
|
|
|
|
|
* 无 path 的特殊入口(如 cs_transfer 传方弹窗)抛给壳页按 code 处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
onItemTap(item) {
|
|
|
|
|
|
if (this.api.reportUsage) {
|
|
|
|
|
|
// 上报是辅助行为:不等待、不处理失败,绝不阻塞跳转
|
|
|
|
|
|
this.api.reportUsage(item.key).catch(() => {});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!item.path) {
|
|
|
|
|
|
this.$emit('special', item);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: item.path,
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
// 目标是当前页栈不允许 navigateTo 的页面(如各端首页)时降级 reLaunch
|
|
|
|
|
|
uni.reLaunch({ url: item.path });
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 长按入口:弹收藏/取消收藏操作单 */
|
|
|
|
|
|
onItemLongpress(item) {
|
|
|
|
|
|
const isFav = this.favCodes.indexOf(item.key) !== -1;
|
|
|
|
|
|
uni.showActionSheet({
|
|
|
|
|
|
itemList: [isFav ? '从我的常用移除' : '添加到我的常用'],
|
|
|
|
|
|
success: (r) => {
|
|
|
|
|
|
if (r.tapIndex === 0) this.doToggleFavorite(item);
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/** 打开全部入口编辑抽屉(悬浮按钮) */
|
|
|
|
|
|
openEditor() {
|
|
|
|
|
|
if (this.$refs.favEditor) this.$refs.favEditor.open();
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 批量保存后刷新收藏状态,长按菜单文案与首页常用保持一致 */
|
|
|
|
|
|
onEditorSaved() {
|
|
|
|
|
|
this.refreshUserData();
|
|
|
|
|
|
},
|
2026-08-12 08:57:35 +08:00
|
|
|
|
/** 收藏切换:成功后按后端返回状态提示并刷新常用列表 */
|
|
|
|
|
|
doToggleFavorite(item) {
|
|
|
|
|
|
if (!this.api.toggleFavorite) return;
|
|
|
|
|
|
this.api.toggleFavorite(item.key).then(({ res, data, ok }) => {
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
const added = !!(data && data.is_favorite);
|
|
|
|
|
|
uni.showToast({ title: added ? '已添加到常用' : '已从常用移除', icon: 'none' });
|
|
|
|
|
|
this.refreshUserData();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({ title: (res && res.message) || '操作失败,请稍后重试', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
/* 纵向 flex:搜索/tabs 固定高度,swiper 吃满剩余空间(swiper 必须有确定高度才能滚动) */
|
|
|
|
|
|
.wf-wrap {
|
2026-08-18 08:53:16 +08:00
|
|
|
|
position: relative;
|
2026-08-12 08:57:35 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-search {
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
padding: 20rpx 24rpx 12rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-tabs {
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
background: #FFFFFF;
|
|
|
|
|
|
border-bottom: 1rpx solid rgba(60, 60, 67, 0.06);
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-swiper {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-scroll {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-search-scroll {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-page {
|
|
|
|
|
|
padding: 20rpx 24rpx 40rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 分区标题:对齐首页 panel-title 规格但更轻量 */
|
|
|
|
|
|
.wf-group-title {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1E293B;
|
|
|
|
|
|
padding: 8rpx 6rpx 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 金刚区白卡容器:iOS 卡片规格与首页/入口卡一致 */
|
|
|
|
|
|
.wf-panel {
|
|
|
|
|
|
background: #FFFFFF;
|
|
|
|
|
|
border-radius: 32rpx;
|
|
|
|
|
|
border: 1rpx solid rgba(60, 60, 67, 0.08);
|
|
|
|
|
|
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
|
|
|
|
|
padding: 16rpx 12rpx;
|
|
|
|
|
|
margin-bottom: 28rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 4 列金刚区:minmax(0,1fr) 防止长标签撑爆轨道 */
|
|
|
|
|
|
.wf-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
|
|
|
row-gap: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 空态提示条:浅灰圆角块,弱引导 */
|
|
|
|
|
|
.wf-hint {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background: rgba(60, 60, 67, 0.04);
|
|
|
|
|
|
border-radius: 24rpx;
|
|
|
|
|
|
padding: 36rpx 24rpx;
|
|
|
|
|
|
margin-bottom: 28rpx;
|
|
|
|
|
|
text {
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #8E8E93;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 加载/失败/空配置居中态 */
|
|
|
|
|
|
.wf-center {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-center-text {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #8E8E93;
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-retry {
|
|
|
|
|
|
margin-top: 24rpx;
|
|
|
|
|
|
padding: 14rpx 48rpx;
|
|
|
|
|
|
background: #6ACDBB;
|
|
|
|
|
|
border-radius: 40rpx;
|
|
|
|
|
|
transition: opacity 0.2s ease;
|
|
|
|
|
|
text {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.wf-retry-hover {
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
2026-08-18 08:53:16 +08:00
|
|
|
|
/* 悬浮添加:贴内容区右下,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;
|
|
|
|
|
|
}
|
2026-08-12 08:57:35 +08:00
|
|
|
|
</style>
|