feat: 小程序与管理端迭代(神仙AI、了解自己CRM、AI测试入口、报表与分润等)
Made-with: Cursor
This commit is contained in:
@@ -1,28 +1,65 @@
|
||||
// 自定义 tabBar(不使用微信内置):灰线 + 三栏,中间为浮起圆钮
|
||||
// 自定义 TabBar:完全由后台配置驱动
|
||||
// 数据来源:app.globalData.tabBar.items(GET /api/mp/tabbar)
|
||||
// 兜底:若配置缺失,使用默认 4 项(首页·拍摄·神仙AI·我)
|
||||
|
||||
const DEFAULT_ITEMS = [
|
||||
{ pagePath: 'pages/index/index', text: '首页', iconKey: 'home', iconUrl: '', highlight: false },
|
||||
{ pagePath: 'pages/index/camera', text: '拍摄', iconKey: 'camera', iconUrl: '', highlight: true },
|
||||
{ pagePath: 'pages/ai-chat/index', text: '神仙AI', iconKey: 'ai', iconUrl: '/images/shenxian-oldman-circle.png', highlight: false },
|
||||
{ pagePath: 'pages/profile/index', text: '我', iconKey: 'profile', iconUrl: '', highlight: false }
|
||||
]
|
||||
|
||||
Component({
|
||||
data: {
|
||||
selected: 0,
|
||||
/** 审核模式 */
|
||||
reviewMode: false,
|
||||
/** 为 true 时隐藏中间「拍摄」:审核模式 或 企业权限关闭 face */
|
||||
hideMiddleFab: false,
|
||||
list: [
|
||||
{ pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' },
|
||||
{ pagePath: '/pages/index/camera', text: '拍摄', textKey: 'camera', icon: 'camera' },
|
||||
{ pagePath: '/pages/profile/index', text: '我的', textKey: 'profile', icon: 'user' }
|
||||
]
|
||||
items: [],
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.updateSelected()
|
||||
this.refreshFromConfig()
|
||||
}
|
||||
},
|
||||
|
||||
pageLifetimes: {
|
||||
show() {
|
||||
this.updateSelected()
|
||||
this.refreshFromConfig()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
refreshFromConfig() {
|
||||
try {
|
||||
const app = getApp() || {}
|
||||
const gd = app.globalData || {}
|
||||
const cfg = gd.tabBar && Array.isArray(gd.tabBar.items) ? gd.tabBar.items : null
|
||||
const rawItems = (cfg && cfg.length >= 2) ? cfg.slice() : DEFAULT_ITEMS.slice()
|
||||
const items = rawItems.map((it) => ({
|
||||
...it,
|
||||
// 仅神仙AI使用指定 logo;其他图标保持后台配置
|
||||
iconUrl: it.iconKey === 'ai' ? '/images/shenxian-oldman-circle.png' : (it.iconUrl || '')
|
||||
}))
|
||||
|
||||
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
|
||||
const ep = gd.enterprisePermissions
|
||||
const faceOff = !!(ep && ep.face === false)
|
||||
const hideMiddleFab = reviewMode || faceOff
|
||||
|
||||
// 审核模式 / face 关闭时:隐藏所有 highlight=true 的 Tab
|
||||
const effectiveItems = items.filter(it => !(hideMiddleFab && it.highlight))
|
||||
|
||||
this.setData({
|
||||
items: effectiveItems,
|
||||
reviewMode,
|
||||
hideMiddleFab,
|
||||
}, () => this.updateSelected())
|
||||
} catch (e) {
|
||||
this.setData({ items: DEFAULT_ITEMS, reviewMode: false, hideMiddleFab: false }, () => this.updateSelected())
|
||||
}
|
||||
},
|
||||
|
||||
updateSelected() {
|
||||
try {
|
||||
const pages = getCurrentPages()
|
||||
@@ -30,35 +67,41 @@ Component({
|
||||
const currentPage = pages[pages.length - 1]
|
||||
if (!currentPage || !currentPage.route) return
|
||||
const url = currentPage.route
|
||||
const gd = getApp().globalData || {}
|
||||
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
|
||||
const ep = gd.enterprisePermissions
|
||||
const hideMiddleFab = reviewMode || !!(ep && ep.face === false)
|
||||
const items = this.data.items || []
|
||||
|
||||
let selected = 0
|
||||
if (url === 'pages/index/index' || url === 'pages/enterprise/index') {
|
||||
selected = 0
|
||||
} else if (url === 'pages/index/camera') {
|
||||
selected = hideMiddleFab ? 0 : 1
|
||||
} else if (url === 'pages/profile/index') {
|
||||
selected = 2
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const path = (items[i].pagePath || '').replace(/^\//, '')
|
||||
if (url === path) { selected = i; break }
|
||||
// 企业首页归一到"首页"Tab
|
||||
if (url === 'pages/enterprise/index' && path === 'pages/index/index') { selected = i; break }
|
||||
if (url === 'pages/index/camera' && items[i].highlight) { selected = i; break }
|
||||
}
|
||||
this.setData({ selected, reviewMode, hideMiddleFab })
|
||||
} catch (error) {
|
||||
console.error('updateSelected error:', error)
|
||||
this.setData({ selected: 0, reviewMode: false, hideMiddleFab: false })
|
||||
this.setData({ selected })
|
||||
} catch (err) {
|
||||
this.setData({ selected: 0 })
|
||||
}
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const index = parseInt(e.currentTarget.dataset.index, 10)
|
||||
let url = e.currentTarget.dataset.path
|
||||
const it = (this.data.items || [])[index]
|
||||
if (!it) return
|
||||
|
||||
const gd = getApp().globalData || {}
|
||||
const faceOff = !!(gd.enterprisePermissions && gd.enterprisePermissions.face === false)
|
||||
if (index === 1 && (!!(gd.reviewMode || gd.maintenanceMode) || faceOff)) {
|
||||
return
|
||||
// 中间浮钮在审核模式下被 refreshFromConfig 过滤掉了,不会到这里
|
||||
|
||||
// 神仙 AI Tab 的轻量埋点
|
||||
if ((it.iconKey === 'ai' || /ai-chat/.test(it.pagePath))) {
|
||||
try {
|
||||
const analytics = require('../utils/analytics.js')
|
||||
if (analytics && typeof analytics.track === 'function') {
|
||||
analytics.track('tap_tab_ai_chat', {})
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
// 首页点击:企业作用域走企业首页(保留旧行为)
|
||||
if (it.pagePath === 'pages/index/index' || it.iconKey === 'home') {
|
||||
try {
|
||||
const app = getApp()
|
||||
const scope = (app && app.globalData && app.globalData.appScope) || 'personal'
|
||||
@@ -70,7 +113,14 @@ Component({
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
wx.switchTab({ url })
|
||||
const url = '/' + it.pagePath.replace(/^\//, '')
|
||||
wx.switchTab({
|
||||
url,
|
||||
fail: () => {
|
||||
// 非 tabBar 页面 fallback 到 navigateTo
|
||||
wx.navigateTo({ url })
|
||||
}
|
||||
})
|
||||
this.setData({ selected: index })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
<!-- 自定义 tabBar:顶部分割线 + 三栏;中间单独一列避免浮钮与左右叠层 -->
|
||||
<!-- 自定义 TabBar:由 globalData.tabBar 动态渲染;支持 iconUrl 图片覆盖 SVG;highlight=中间浮钮 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-bar-line"></view>
|
||||
<view class="tab-bar-inner {{hideMiddleFab ? 'tab-bar-inner--audit' : ''}}">
|
||||
<view
|
||||
class="tab-item {{selected === 0 ? 'active' : ''}}"
|
||||
data-index="0"
|
||||
data-path="/pages/index/index"
|
||||
bindtap="switchTab"
|
||||
>
|
||||
<image class="tab-icon" src="{{selected === 0 ? '/images/home-active.png' : '/images/home.png'}}" mode="aspectFit" />
|
||||
<text class="tab-text">首页</text>
|
||||
</view>
|
||||
<view class="tab-bar-inner tab-bar-count-{{items.length}}">
|
||||
<block wx:for="{{items}}" wx:key="pagePath" wx:for-item="it">
|
||||
<!-- 中间浮钮(highlight=true) -->
|
||||
<view wx:if="{{it.highlight}}" class="tab-slot-middle">
|
||||
<view
|
||||
class="middle-fab {{selected === index ? 'active' : ''}}"
|
||||
data-index="{{index}}"
|
||||
bindtap="switchTab"
|
||||
>
|
||||
<view class="center-circle">
|
||||
<image wx:if="{{it.iconUrl}}" class="center-img" src="{{it.iconUrl}}" mode="aspectFit" />
|
||||
<view wx:else class="center-svg center-svg--{{it.iconKey || 'camera'}}"></view>
|
||||
</view>
|
||||
<text class="tab-text tab-text-fab">{{it.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 中间列:非审核模式显示「拍摄」 -->
|
||||
<view class="tab-slot-middle" wx:if="{{!hideMiddleFab}}">
|
||||
<!-- 普通 Tab item -->
|
||||
<view
|
||||
class="middle-fab {{selected === 1 ? 'active' : ''}}"
|
||||
data-index="1"
|
||||
data-path="/pages/index/camera"
|
||||
wx:else
|
||||
class="tab-item {{selected === index ? 'active' : ''}} tab-item--{{it.iconKey || 'home'}}"
|
||||
data-index="{{index}}"
|
||||
bindtap="switchTab"
|
||||
>
|
||||
<view class="center-circle">
|
||||
<image class="center-icon" src="{{selected === 1 ? '/images/camera-active.png' : '/images/camera.png'}}" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="tab-text tab-text-fab">拍摄</text>
|
||||
<image
|
||||
wx:if="{{it.iconUrl}}"
|
||||
class="tab-icon-img {{selected === index ? 'tab-icon-img--active' : ''}}"
|
||||
src="{{it.iconUrl}}"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view
|
||||
wx:else
|
||||
class="tab-icon tab-icon--{{it.iconKey || 'home'}} {{selected === index ? 'tab-icon--active' : ''}}"
|
||||
></view>
|
||||
<text class="tab-text">{{it.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tab-item {{selected === 2 ? 'active' : ''}}"
|
||||
data-index="2"
|
||||
data-path="/pages/profile/index"
|
||||
bindtap="switchTab"
|
||||
>
|
||||
<image class="tab-icon" src="{{selected === 2 ? '/images/user-active.png' : '/images/user.png'}}" mode="aspectFit" />
|
||||
<text class="tab-text">我的</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 自定义 tabBar:白底、顶部分割线、中间浮起圆钮 */
|
||||
/* 自定义 TabBar:白底、顶部分割线、支持 highlight 圆钮 · 动态数量 */
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
@@ -18,19 +18,16 @@
|
||||
.tab-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: space-around;
|
||||
min-height: 120rpx;
|
||||
padding: 12rpx 12rpx 10rpx;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 审核模式仅「首页」「我的」两栏时,左右均匀留白 */
|
||||
.tab-bar-inner--audit {
|
||||
justify-content: space-between;
|
||||
padding-left: 56rpx;
|
||||
padding-right: 56rpx;
|
||||
}
|
||||
.tab-bar-count-3 { padding-left: 40rpx; padding-right: 40rpx; }
|
||||
.tab-bar-count-4,
|
||||
.tab-bar-count-5 { padding-left: 12rpx; padding-right: 12rpx; }
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
@@ -47,6 +44,32 @@
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-bottom: 4rpx;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 44rpx 44rpx;
|
||||
}
|
||||
|
||||
/* 图片形态的图标(iconUrl 传图片时使用,保留圆角便于 logo 类图片显示) */
|
||||
.tab-icon-img {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
margin-bottom: 4rpx;
|
||||
border-radius: 12rpx;
|
||||
filter: grayscale(60%);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.tab-icon-img--active {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4rpx 10rpx rgba(124, 58, 237, 0.25);
|
||||
}
|
||||
|
||||
/* 中间浮钮的图片形态 */
|
||||
.center-img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
@@ -58,7 +81,43 @@
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
/* 中间列:独立层叠上下文,浮钮不盖住左右 tab */
|
||||
/* ===== 图标:home ===== */
|
||||
.tab-icon--home {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M3 12l9-9 9 9'/><path d='M5 10v10h14V10'/><path d='M10 20v-6h4v6'/></svg>");
|
||||
}
|
||||
.tab-icon--home.tab-icon--active,
|
||||
.tab-item.active .tab-icon--home {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237c3aed' stroke='%237c3aed' stroke-width='1.6' stroke-linejoin='round'><path d='M3 12l9-9 9 9'/><path d='M5 10v10h14V10'/><path d='M10 20v-6h4v6' fill='none' stroke='%23ffffff'/></svg>");
|
||||
}
|
||||
|
||||
/* ===== 图标:ai (sparkle) ===== */
|
||||
.tab-icon--ai {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1'/><circle cx='12' cy='12' r='3'/></svg>");
|
||||
}
|
||||
.tab-icon--ai.tab-icon--active,
|
||||
.tab-item.active .tab-icon--ai {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237c3aed' stroke='%237c3aed' stroke-width='1.6' stroke-linejoin='round'><path d='M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1' stroke='%237c3aed' fill='none' stroke-linecap='round'/><circle cx='12' cy='12' r='3.5' fill='%237c3aed'/></svg>");
|
||||
}
|
||||
|
||||
/* ===== 图标:profile ===== */
|
||||
.tab-icon--profile {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='8' r='4'/><path d='M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8'/></svg>");
|
||||
}
|
||||
.tab-icon--profile.tab-icon--active,
|
||||
.tab-item.active .tab-icon--profile {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237c3aed' stroke='%237c3aed' stroke-width='1.6' stroke-linejoin='round'><circle cx='12' cy='8' r='4'/><path d='M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8' fill='%237c3aed'/></svg>");
|
||||
}
|
||||
|
||||
/* ===== 图标:camera(作为普通 Tab 时用) ===== */
|
||||
.tab-icon--camera {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M4 7h3l2-3h6l2 3h3v13H4z'/><circle cx='12' cy='13' r='4'/></svg>");
|
||||
}
|
||||
.tab-icon--camera.tab-icon--active,
|
||||
.tab-item.active .tab-icon--camera {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237c3aed' stroke='%237c3aed' stroke-width='1.6' stroke-linejoin='round'><path d='M4 7h3l2-3h6l2 3h3v13H4z'/><circle cx='12' cy='13' r='4' fill='%23ffffff' stroke='%237c3aed'/></svg>");
|
||||
}
|
||||
|
||||
/* ===== 中间浮钮 ===== */
|
||||
.tab-slot-middle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -71,7 +130,6 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* 浮钮:整体上移,更突出圆形快门、减少与底栏顶边重叠感 */
|
||||
.middle-fab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -104,9 +162,19 @@
|
||||
box-shadow: 0 4rpx 20rpx rgba(124, 58, 237, 0.6);
|
||||
}
|
||||
|
||||
.center-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
.center-svg {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 42rpx 42rpx;
|
||||
}
|
||||
|
||||
.center-svg--camera {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 7h3l2-3h6l2 3h3v13H4z'/><circle cx='12' cy='13' r='4'/></svg>");
|
||||
}
|
||||
.center-svg--ai {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1'/><circle cx='12' cy='12' r='3.5' fill='%23ffffff'/></svg>");
|
||||
}
|
||||
|
||||
.tab-text-fab {
|
||||
|
||||
Reference in New Issue
Block a user