优化细节

This commit is contained in:
2026-02-06 12:06:33 +08:00
parent b7545173c6
commit d00f0b7412
83 changed files with 3901 additions and 2354 deletions

View File

@@ -57,12 +57,11 @@
@tab-close="onTabClose"
@close-other="onCloseOther"
@close-all="onCloseAll"
@refresh="onRefresh"
/>
<!-- 内容展示区 (内部路由渲染) -->
<view class="content-scroll">
<view class="content-inner" :style="{ padding: isMobile ? '12px' : '16px' }">
<view class="content-inner" :class="{ 'is-mobile': isMobile }">
<component :is="currentComponent" />
</view>
<AdminFooter />
@@ -400,7 +399,7 @@ onMounted(() => {
flex-direction: row;
width: 100%;
min-height: 100vh;
background: #f0f2f5;
background: #f5f7f9;
position: relative;
}
@@ -444,7 +443,7 @@ onMounted(() => {
flex-direction: column;
min-height: 100vh;
transition: margin-left 300ms ease;
background: #f0f2f5;
background: #f5f7f9;
width: 100%;
}
@@ -474,11 +473,14 @@ onMounted(() => {
flex: 1;
overflow-y: scroll;
overflow-x: auto; /* 允许横向滚动,兼容极端窄屏 */
background: #f0f2f5;
background: #f5f7f9;
}
.content-inner {
min-height: calc(100vh - 120px);
padding: 16px;
padding: 12px 14px;
}
.content-inner.is-mobile {
padding: 8px;
}
</style>

View File

@@ -52,8 +52,7 @@ function getIconText(icon: string): string {
'user': '👥',
'product': '📦',
'order': '📜',
'marketing': '📉',
'content': '📝',
'marketing': '📉', 'share': '📢', 'content': '📝',
'finance': '💰',
'statistic': '📊',
'setting': '⚙️',

View File

@@ -225,9 +225,8 @@ const statsData = ref({
<style scoped>
.home-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 兼容旧布局标识,样式逻辑已由 .kpi-grid 接管 */
@@ -237,10 +236,10 @@ const statsData = ref({
/* 图表区样式 */
.chart-section {
margin-top: 16px;
margin-top: 12px;
background-color: #ffffff;
border-radius: 4px;
padding: 20px;
padding: 16px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
@@ -249,7 +248,7 @@ const statsData = ref({
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
margin-bottom: 16px;
}
.header-left {
@@ -320,10 +319,10 @@ const statsData = ref({
}
.bottom-charts {
margin-top: 16px;
margin-top: 12px;
display: flex;
flex-direction: row;
gap: 16px;
gap: 12px;
}
.half-width {

View File

@@ -144,9 +144,12 @@ export const componentMap: Map<string, any> = new Map([
['SettingInterfacePayment', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/payment.uvue'))],
// 分销模块
['DistributionStatistic', PlaceholderPage],
['DistributionList', PlaceholderPage],
['DistributionConfig', PlaceholderPage],
['DistributionPromoter', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/promoter/index.uvue'))],
['DistributionLevel', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/level/index.uvue'))],
['DistributionConfig', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/setting/index.uvue'))],
['DivisionList', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/division/list.uvue'))],
['DivisionAgent', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/division/agent.uvue'))],
['DivisionApply', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/division/apply.uvue'))],
// 客服模块
['KefuList', defineAsyncComponent(() => import('@/pages/mall/admin/kefu/list.uvue'))],

View File

@@ -119,10 +119,11 @@ export const topMenus: TopMenu[] = [
id: 'distribution',
title: '分销',
icon: 'share',
path: '/pages/mall/admin/distribution/statistic',
path: '/pages/mall/admin/distribution/promoter',
order: 6,
groups: [
{ id: 'distribution-manage', title: '', order: 1 }
{ id: 'distribution-manage', title: '', order: 1 },
{ id: 'distribution-division', title: '事业部', order: 2 }
]
},
{
@@ -1070,19 +1071,19 @@ export const routes: RouteRecord[] = [
// ========== 分销模块 ==========
{
id: 'distribution_statistic',
title: '分销统计',
path: '/pages/mall/admin/distribution/statistic',
componentKey: 'DistributionStatistic',
id: 'distribution_promoter',
title: '分销员管理',
path: '/pages/mall/admin/distribution/promoter/index',
componentKey: 'DistributionPromoter',
parentId: 'distribution',
groupId: 'distribution-manage',
order: 1
},
{
id: 'distribution_list',
title: '分销员列表',
path: '/pages/mall/admin/distribution/list',
componentKey: 'DistributionList',
id: 'distribution_level',
title: '分销等级',
path: '/pages/mall/admin/distribution/level/index',
componentKey: 'DistributionLevel',
parentId: 'distribution',
groupId: 'distribution-manage',
order: 2
@@ -1090,12 +1091,39 @@ export const routes: RouteRecord[] = [
{
id: 'distribution_config',
title: '分销设置',
path: '/pages/mall/admin/distribution/config',
path: '/pages/mall/admin/distribution/setting/index',
componentKey: 'DistributionConfig',
parentId: 'distribution',
groupId: 'distribution-manage',
order: 3
},
{
id: 'division_list',
title: '事业部列表',
path: '/pages/mall/admin/distribution/division/list',
componentKey: 'DivisionList',
parentId: 'distribution',
groupId: 'distribution-division',
order: 1
},
{
id: 'division_agent',
title: '代理商列表',
path: '/pages/mall/admin/distribution/division/agent',
componentKey: 'DivisionAgent',
parentId: 'distribution',
groupId: 'distribution-division',
order: 2
},
{
id: 'division_apply',
title: '代理商申请',
path: '/pages/mall/admin/distribution/division/apply',
componentKey: 'DivisionApply',
parentId: 'distribution',
groupId: 'distribution-division',
order: 3
},
// ========== 客服模块 ==========
{

View File

@@ -2,7 +2,7 @@
.kpi-grid {
display: grid !important;
gap: 16px;
gap: 12px;
width: 100%;
box-sizing: border-box;
}
@@ -39,7 +39,7 @@
/* 6-2-1 网格规范 (对标 CRMEB 统计概况) */
.kpi-grid-6 {
display: grid !important;
gap: 16px;
gap: 12px;
width: 100%;
box-sizing: border-box;
}

View File

@@ -528,6 +528,48 @@
"style": {
"navigationBarTitleText": "签到记录"
}
},
{
"path": "distribution/promoter/index",
"style": {
"navigationBarTitleText": "分销员管理",
"navigationStyle": "custom"
}
},
{
"path": "distribution/level/index",
"style": {
"navigationBarTitleText": "分销等级",
"navigationStyle": "custom"
}
},
{
"path": "distribution/setting/index",
"style": {
"navigationBarTitleText": "分销设置",
"navigationStyle": "custom"
}
},
{
"path": "distribution/division/list",
"style": {
"navigationBarTitleText": "事业部列表",
"navigationStyle": "custom"
}
},
{
"path": "distribution/division/agent",
"style": {
"navigationBarTitleText": "代理商列表",
"navigationStyle": "custom"
}
},
{
"path": "distribution/division/apply",
"style": {
"navigationBarTitleText": "代理商申请",
"navigationStyle": "custom"
}
}
]
},

View File

@@ -1,48 +1,48 @@
<template>
<template>
<view class="admin-cms-article">
<view class="content-body">
<!-- 顶部过滤栏 -->
<!-- 椤堕儴杩囨护鏍?-->
<view class="filter-card border-shadow">
<view class="filter-item">
<text class="label-txt">文章分类:</text>
<text class="label-txt">鏂囩珷鍒嗙被:</text>
<view class="select-mock">
<text class="select-val">{{ filterCategory }}</text>
<text class="arrow-down">▼</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="label-txt">文章搜索:</text>
<input class="search-input" placeholder="请输入" v-model="filterKeyword" />
<text class="label-txt">鏂囩珷鎼滅储:</text>
<input class="search-input" placeholder="璇疯緭鍏? v-model="filterKeyword" />
</view>
<view class="btn-query" @click="handleQuery">
<text class="query-txt">查询</text>
<text class="query-txt">鏌ヨ</text>
</view>
</view>
<!-- 主要内容区域 -->
<!-- 涓昏鍐呭鍖哄煙 -->
<view class="table-card border-shadow">
<view class="card-header">
<view class="btn-primary" @click="handleAdd">
<text class="btn-txt">添加文章</text>
<text class="btn-txt">娣诲姞鏂囩珷</text>
</view>
</view>
<!-- 数据表格 -->
<!-- 鏁版嵁琛ㄦ牸 -->
<view class="table-header">
<view class="th col-id"><text class="th-txt">ID</text></view>
<view class="th col-img"><text class="th-txt">文章图片</text></view>
<view class="th col-name"><text class="th-txt">文章名称</text></view>
<view class="th col-link"><text class="th-txt">关联商品</text></view>
<view class="th col-v"><text class="th-txt">浏览量</text></view>
<view class="th col-time"><text class="th-txt">时间</text></view>
<view class="th col-op"><text class="th-txt">操作</text></view>
<view class="th col-img"><text class="th-txt">鏂囩珷鍥剧墖</text></view>
<view class="th col-name"><text class="th-txt">鏂囩珷鍚嶇О</text></view>
<view class="th col-link"><text class="th-txt">鍏宠仈鍟嗗搧</text></view>
<view class="th col-v"><text class="th-txt">娴忚閲?/text></view>
<view class="th col-time"><text class="th-txt">鏃堕棿</text></view>
<view class="th col-op"><text class="th-txt">鎿嶄綔</text></view>
</view>
<view class="table-body">
<view class="table-row" v-for="item in articleList" :key="item.id">
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td col-img">
<view class="img-box"><text class="img-placeholder">🖼️</text></view>
<view class="img-box"><text class="img-placeholder">馃柤锔?/text></view>
</view>
<view class="td col-name"><text class="td-txt">{{ item.name }}</text></view>
<view class="td col-link"><text class="td-txt">{{ item.linkedProduct }}</text></view>
@@ -50,14 +50,14 @@
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
<view class="td col-op">
<view class="op-links">
<text class="link-txt" @click="handleEdit(item)">编辑</text>
<text class="link-txt" @click="handleEdit(item)">缂栬緫</text>
<view class="divider"></view>
<text class="link-txt">关联</text>
<text class="link-txt">鍏宠仈</text>
<view class="divider"></view>
<text class="link-txt danger">删除</text>
<text class="link-txt danger">鍒犻櫎</text>
<view class="divider"></view>
<text class="link-txt">复制链接</text>
<text class="arrow-small">▼</text>
<text class="link-txt">澶嶅埗閾炬帴</text>
<text class="arrow-small">鈻?/text>
</view>
</view>
</view>
@@ -65,28 +65,28 @@
<view class="pagination-bar">
<view class="page-info">
<text class="page-total">{{ articleList.length }} 条</text>
<text class="page-total">鍏?{{ articleList.length }} 鏉?/text>
</view>
</view>
</view>
</view>
<!-- 侧边弹窗 (Drawer) -->
<!-- 渚ц竟寮圭獥 (Drawer) -->
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
<view class="drawer-header">
<view class="tab-item active">
<text class="tab-txt">文章信息</text>
<text class="tab-txt">鏂囩珷淇℃伅</text>
<view class="tab-line"></view>
</view>
<text class="close-btn" @click="closeDrawer">×</text>
<text class="close-btn" @click="closeDrawer"></text>
</view>
<scroll-view class="drawer-body" :scroll-y="true">
<!-- 文章信息区块 -->
<!-- 鏂囩珷淇℃伅鍖哄潡 -->
<view class="section-title">
<view class="title-inner active">
<text class="title-txt">文章信息</text>
<text class="title-txt">鏂囩珷淇℃伅</text>
<view class="title-line"></view>
</view>
</view>
@@ -94,16 +94,16 @@
<view class="form-grid">
<view class="form-row">
<view class="form-col">
<view class="label-box"><text class="required">*</text><text class="label-txt">标题:</text></view>
<view class="label-box"><text class="required">*</text><text class="label-txt">鏍囬:</text></view>
<view class="input-box">
<input class="input-base" v-model="formTitle" placeholder="请输入" />
<input class="input-base" v-model="formTitle" placeholder="璇疯緭鍏? />
<text class="input-count">{{ formTitle.length }}/80</text>
</view>
</view>
<view class="form-col">
<view class="label-box"><text class="label-txt">作者:</text></view>
<view class="label-box"><text class="label-txt">浣滆€?</text></view>
<view class="input-box">
<input class="input-base" v-model="formAuthor" placeholder="请输入" />
<input class="input-base" v-model="formAuthor" placeholder="璇疯緭鍏? />
<text class="input-count">{{ formAuthor.length }}/10</text>
</view>
</view>
@@ -111,18 +111,18 @@
<view class="form-row mt-20">
<view class="form-col">
<view class="label-box"><text class="required">*</text><text class="label-txt">文章分类:</text></view>
<view class="label-box"><text class="required">*</text><text class="label-txt">鏂囩珷鍒嗙被:</text></view>
<view class="input-box">
<view class="select-mock">
<text class="select-val">{{ formCategory || '请选择' }}</text>
<text class="arrow-down">▼</text>
<text class="select-val">{{ formCategory || '璇烽€夋嫨' }}</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
</view>
<view class="form-col">
<view class="label-box"><text class="label-txt">文章简介:</text></view>
<view class="label-box"><text class="label-txt">鏂囩珷绠€浠?</text></view>
<view class="input-box">
<textarea class="textarea-base" v-model="formIntro" placeholder="请输入" />
<textarea class="textarea-base" v-model="formIntro" placeholder="璇疯緭鍏? />
<text class="input-count">{{ formIntro.length }}/300</text>
</view>
</view>
@@ -130,77 +130,77 @@
<view class="form-row mt-20">
<view class="form-col full">
<view class="label-box"><text class="required">*</text><text class="label-txt">图文封面:</text></view>
<view class="label-box"><text class="required">*</text><text class="label-txt">鍥炬枃灏侀潰:</text></view>
<view class="upload-container">
<view class="upload-btn">
<text class="plus-icon">+</text>
</view>
<text class="tip-txt mt-10">建议尺寸500 x 312 px</text>
<text class="tip-txt mt-10">寤鸿灏哄锛?00 x 312 px</text>
</view>
</view>
</view>
</view>
<!-- 文章内容区块 -->
<!-- 鏂囩珷鍐呭鍖哄潡 -->
<view class="section-title mt-40">
<view class="title-inner active">
<text class="title-txt">文章内容</text>
<text class="title-txt">鏂囩珷鍐呭</text>
<view class="title-line"></view>
</view>
</view>
<view class="editor-section">
<view class="label-box mb-10"><text class="required">*</text><text class="label-txt">文章内容:</text></view>
<view class="label-box mb-10"><text class="required">*</text><text class="label-txt">鏂囩珷鍐呭:</text></view>
<view class="rich-editor-mock">
<view class="editor-toolbar">
<text class="tool-ic">HTML</text>
<text class="tool-ic">H</text>
<text class="tool-ic">B</text>
<text class="tool-ic">T↕</text>
<text class="tool-ic">T鈫?/text>
<text class="tool-ic">F</text>
<text class="tool-ic">I</text>
<text class="tool-ic">U</text>
<text class="tool-ic">S</text>
<text class="tool-ic-img">🖼️</text>
<text class="tool-ic-img">🎬</text>
<text class="tool-ic-img">馃柤锔?/text>
<text class="tool-ic-img">馃幀</text>
</view>
<view class="editor-content"></view>
</view>
</view>
<!-- 其他设置 -->
<!-- 鍏朵粬璁剧疆 -->
<view class="section-title mt-40">
<view class="title-inner active">
<text class="title-txt">其他设置</text>
<text class="title-txt">鍏朵粬璁剧疆</text>
<view class="title-line"></view>
</view>
</view>
<view class="settings-section">
<view class="form-item">
<view class="label-box-wide"><text class="label-txt">banner显示:</text></view>
<view class="label-box-wide"><text class="label-txt">banner鏄剧ず:</text></view>
<view class="radio-group">
<view class="radio-item" @click="formBanner = true">
<view :class="['radio-circle', formBanner ? 'checked' : '']"><view v-if="formBanner" class="radio-in"></view></view>
<text class="radio-la">显示</text>
<text class="radio-la">鏄剧ず</text>
</view>
<view class="radio-item" @click="formBanner = false">
<view :class="['radio-circle', !formBanner ? 'checked' : '']"><view v-if="!formBanner" class="radio-in"></view></view>
<text class="radio-la">不显示</text>
<text class="radio-la">涓嶆樉绀?/text>
</view>
</view>
</view>
<view class="form-item mt-20">
<view class="label-box-wide"><text class="label-txt">热门文章:</text></view>
<view class="label-box-wide"><text class="label-txt">鐑棬鏂囩珷:</text></view>
<view class="radio-group">
<view class="radio-item" @click="formHot = true">
<view :class="['radio-circle', formHot ? 'checked' : '']"><view v-if="formHot" class="radio-in"></view></view>
<text class="radio-la">显示</text>
<text class="radio-la">鏄剧ず</text>
</view>
<view class="radio-item" @click="formHot = false">
<view :class="['radio-circle', !formHot ? 'checked' : '']"><view v-if="!formHot" class="radio-in"></view></view>
<text class="radio-la">不显示</text>
<text class="radio-la">涓嶆樉绀?/text>
</view>
</view>
</view>
@@ -208,7 +208,7 @@
<view class="submit-container mt-40">
<view class="btn-submit" @click="handleConfirm">
<text class="submit-txt">提交</text>
<text class="submit-txt">鎻愪氦</text>
</view>
</view>
</scroll-view>
@@ -220,11 +220,11 @@
<script setup lang="uts">
import { ref } from 'vue'
const filterCategory = ref('全部')
const filterCategory = ref('鍏ㄩ儴')
const filterKeyword = ref('')
const articleList = ref([
{ id: '240', name: '赋能消费 | 卷狗优选迈向文化消费新时代', linkedProduct: '', views: '3349', time: '2025-04-01 16:34' },
{ id: '237', name: '把重要的日子放在桌面', linkedProduct: '2024新款吹风机...', views: '260', time: '2025-04-01 16:32' }
{ id: '240', name: '璧嬭兘娑堣垂 | 鍗风嫍浼橀€夎繄鍚戞枃鍖栨秷璐规柊鏃朵唬', linkedProduct: '', views: '3349', time: '2025-04-01 16:34' },
{ id: '237', name: '鎶婇噸瑕佺殑鏃ュ瓙鏀惧湪妗岄潰', linkedProduct: '2024鏂版鍚归鏈?..', views: '260', time: '2025-04-01 16:32' }
])
const showDrawer = ref(false)
@@ -249,9 +249,9 @@ const handleAdd = () => {
const handleEdit = (item: any) => {
formTitle.value = item.name
formAuthor.value = '管理员'
formCategory.value = '全部'
formIntro.value = '这是一段文章简介...'
formAuthor.value = '绠$悊鍛?
formCategory.value = '鍏ㄩ儴'
formIntro.value = '杩欐槸涓€娈垫枃绔犵畝浠?..'
formBanner.value = false
formHot.value = true
isClosing.value = false
@@ -310,7 +310,7 @@ const handleQuery = () => { console.log('Querying...') }
.page-total { font-size: 13px; color: #606266; margin-right: 15px; }
.drawer-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.4); z-index: 2000; transition: opacity 0.3s; }
.mask-fade-out { opacity: 0; }
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; background-color: #fff; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
.slide-out { animation: slideOut 0.3s ease forwards; }
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }
@@ -353,7 +353,7 @@ const handleQuery = () => { console.log('Querying...') }
.editor-toolbar { height: 44px; background-color: #fafafa; border-bottom: 1px solid #dcdee2; display: flex; flex-direction: row; align-items: center; padding: 0 15px; gap: 20px; flex-wrap: wrap; }
.tool-ic { font-size: 14px; color: #515a6e; cursor: pointer; }
.tool-ic-img { font-size: 18px; cursor: pointer; }
.editor-content { flex: 1; background-color: #fff; }
.editor-content { flex: 1; }
.settings-section { display: flex; flex-direction: column; }
.form-item { display: flex; flex-direction: row; align-items: center; }
@@ -380,3 +380,4 @@ const handleQuery = () => { console.log('Querying...') }
.mt-10 { margin-top: 10px; }
.mb-10 { margin-bottom: 10px; }
</style>

View File

@@ -1,39 +1,39 @@
<template>
<template>
<view class="admin-cms-category">
<view class="content-body">
<!-- 顶部过滤栏 -->
<!-- 椤堕儴杩囨护鏍?-->
<view class="filter-card border-shadow">
<view class="filter-item">
<text class="label-txt">是否显示:</text>
<text class="label-txt">鏄惁鏄剧ず:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow-down">▼</text>
<text class="select-val">璇烽€夋嫨</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="label-txt">分类名称:</text>
<input class="search-input" placeholder="请输入分类名称" v-model="filterKeyword" />
<text class="label-txt">鍒嗙被鍚嶇О:</text>
<input class="search-input" placeholder="璇疯緭鍏ュ垎绫诲悕绉? v-model="filterKeyword" />
</view>
<view class="btn-query" @click="handleQuery">
<text class="query-txt">查询</text>
<text class="query-txt">鏌ヨ</text>
</view>
</view>
<!-- 主要内容区域 -->
<!-- 涓昏鍐呭鍖哄煙 -->
<view class="table-card border-shadow">
<view class="card-header">
<view class="btn-primary" @click="handleAdd">
<text class="btn-txt">添加文章分类</text>
<text class="btn-txt">娣诲姞鏂囩珷鍒嗙被</text>
</view>
</view>
<!-- 数据表格 -->
<!-- 鏁版嵁琛ㄦ牸 -->
<view class="table-header">
<view class="th col-id"><text class="th-txt">ID</text></view>
<view class="th col-name"><text class="th-txt">分类名称</text></view>
<view class="th col-img"><text class="th-txt">分类图片</text></view>
<view class="th col-status"><text class="th-txt">状态</text></view>
<view class="th col-op"><text class="th-txt">操作</text></view>
<view class="th col-name"><text class="th-txt">鍒嗙被鍚嶇О</text></view>
<view class="th col-img"><text class="th-txt">鍒嗙被鍥剧墖</text></view>
<view class="th col-status"><text class="th-txt">鐘舵€?/text></view>
<view class="th col-op"><text class="th-txt">鎿嶄綔</text></view>
</view>
<view class="table-body">
@@ -50,11 +50,11 @@
</view>
<view class="td col-op">
<view class="op-links">
<text class="link-txt" @click="handleEdit(item)">编辑</text>
<text class="link-txt" @click="handleEdit(item)">缂栬緫</text>
<view class="divider"></view>
<text class="link-txt danger">删除</text>
<text class="link-txt danger">鍒犻櫎</text>
<view class="divider"></view>
<text class="link-txt">查看文章</text>
<text class="link-txt">鏌ョ湅鏂囩珷</text>
</view>
</view>
</view>
@@ -62,73 +62,73 @@
</view>
</view>
<!-- 侧边弹窗 (Drawer) -->
<!-- 渚ц竟寮圭獥 (Drawer) -->
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
<view class="drawer-header">
<text class="drawer-title">添加分类</text>
<text class="close-btn" @click="closeDrawer">×</text>
<text class="drawer-title">娣诲姞鍒嗙被</text>
<text class="close-btn" @click="closeDrawer"></text>
</view>
<scroll-view class="drawer-body" :scroll-y="true">
<view class="form-item row">
<view class="label-box"><text class="label-txt">上级分类:</text></view>
<view class="label-box"><text class="label-txt">涓婄骇鍒嗙被:</text></view>
<view class="input-box">
<view class="select-mock">
<text class="select-val">顶级分类</text>
<text class="arrow-down">▼</text>
<text class="select-val">椤剁骇鍒嗙被</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
</view>
<view class="form-item row">
<view class="label-box"><text class="required">*</text><text class="label-txt">分类名称:</text></view>
<view class="label-box"><text class="required">*</text><text class="label-txt">鍒嗙被鍚嶇О:</text></view>
<view class="input-box">
<input class="input-base" v-model="formName" placeholder="请输入分类名称" />
<input class="input-base" v-model="formName" placeholder="璇疯緭鍏ュ垎绫诲悕绉? />
</view>
</view>
<view class="form-item row align-start">
<view class="label-box pt-10"><text class="required">*</text><text class="label-txt">分类简介:</text></view>
<view class="label-box pt-10"><text class="required">*</text><text class="label-txt">鍒嗙被绠€浠?</text></view>
<view class="input-box">
<textarea class="textarea-mini" v-model="formDesc" placeholder="请输入分类简介" />
<textarea class="textarea-mini" v-model="formDesc" placeholder="璇疯緭鍏ュ垎绫荤畝浠? />
</view>
</view>
<view class="form-item row">
<view class="label-box"><text class="label-txt">分类图片:</text></view>
<view class="label-box"><text class="label-txt">鍒嗙被鍥剧墖:</text></view>
<view class="input-box">
<view class="upload-btn">
<view class="img-icon">🖼️</view>
<view class="img-icon">馃柤锔?/view>
</view>
</view>
</view>
<view class="form-item row">
<view class="label-box"><text class="label-txt">排序:</text></view>
<view class="label-box"><text class="label-txt">鎺掑簭:</text></view>
<view class="input-box">
<input class="input-base" type="number" v-model="formSort" />
</view>
</view>
<view class="form-item row">
<view class="label-box"><text class="label-txt">状态:</text></view>
<view class="label-box"><text class="label-txt">鐘舵€?</text></view>
<view class="radio-group">
<view class="radio-item" @click="formStatus = true">
<view :class="['radio-circle', formStatus ? 'checked' : '']"><view v-if="formStatus" class="radio-in"></view></view>
<text class="radio-la">显示</text>
<text class="radio-la">鏄剧ず</text>
</view>
<view class="radio-item" @click="formStatus = false">
<view :class="['radio-circle', !formStatus ? 'checked' : '']"><view v-if="!formStatus" class="radio-in"></view></view>
<text class="radio-la">隐藏</text>
<text class="radio-la">闅愯棌</text>
</view>
</view>
</view>
</scroll-view>
<view class="drawer-footer">
<view class="btn-cancel" @click="closeDrawer"><text class="cancel-txt">取消</text></view>
<view class="btn-confirm" @click="handleConfirm"><text class="confirm-txt">确定</text></view>
<view class="btn-cancel" @click="closeDrawer"><text class="cancel-txt">鍙栨秷</text></view>
<view class="btn-confirm" @click="handleConfirm"><text class="confirm-txt">纭畾</text></view>
</view>
</view>
</view>
@@ -140,9 +140,9 @@ import { ref } from 'vue'
const filterKeyword = ref('')
const categoryList = ref([
{ id: '181', name: '购物心得', status: true },
{ id: '180', name: '消费文化', status: true },
{ id: '179', name: '品牌资讯', status: true }
{ id: '181', name: '璐墿蹇冨緱', status: true },
{ id: '180', name: '娑堣垂鏂囧寲', status: true },
{ id: '179', name: '鍝佺墝璧勮', status: true }
])
const showDrawer = ref(false)
@@ -163,7 +163,7 @@ const handleAdd = () => {
const handleEdit = (item: any) => {
formName.value = item.name
// 模拟填充其他字段
// 妯℃嫙濉厖鍏朵粬瀛楁
formDesc.value = ''
formSort.value = 0
formStatus.value = item.status
@@ -224,7 +224,7 @@ const handleQuery = () => { console.log('Querying...') }
.divider { width: 1px; height: 12px; background-color: #e8eaec; margin: 0 10px; }
.drawer-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.4); z-index: 2000; transition: opacity 0.3s; }
.mask-fade-out { opacity: 0; }
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; background-color: #fff; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
.slide-out { animation: slideOut 0.3s ease forwards; }
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }
@@ -264,3 +264,4 @@ const handleQuery = () => { console.log('Querying...') }
.cancel-txt { font-size: 14px; color: #606266; }
.confirm-txt { font-size: 14px; color: #fff; }
</style>

View File

@@ -1,58 +1,58 @@
<template>
<template>
<view class="admin-decoration-category">
<!-- 顶部标题与按钮 -->
<!-- 椤堕儴鏍囬涓庢寜閽?-->
<view class="page-header border-shadow">
<view class="header-left">
<text class="page-title">商品分类</text>
<text class="page-title">鍟嗗搧鍒嗙被</text>
</view>
<view class="header-right">
<view class="btn-primary" @click="handleSave">
<text class="btn-txt">保存</text>
<text class="btn-txt">淇濆瓨</text>
</view>
<view class="btn-ghost" @click="handleReset">
<text class="ghost-txt">重置</text>
<text class="ghost-txt">閲嶇疆</text>
</view>
</view>
</view>
<!-- 分类展示区域 -->
<!-- 鍒嗙被灞曠ず鍖哄煙 -->
<view class="content-container">
<view class="style-list">
<!-- 样式1 -->
<!-- 鏍峰紡1 -->
<view class="style-card-wrapper">
<view :class="['style-card', selectedStyle === 1 ? 'active' : '']" @click="selectedStyle = 1">
<view class="phone-mock">
<view class="phone-header">
<text class="p-title">产品分类</text>
<text class="p-dots">••• Ⓞ</text>
<text class="p-title">浜у搧鍒嗙被</text>
<text class="p-dots">鈥⑩€⑩€?鈸?/text>
</view>
<view class="phone-body">
<view class="search-bar">
<text class="ic-search">🔍</text>
<text class="search-ph">点击搜索商品信息</text>
<text class="ic-search">馃攳</text>
<text class="search-ph">鐐瑰嚮鎼滅储鍟嗗搧淇℃伅</text>
</view>
<view class="style1-content">
<view class="sidebar-mock">
<text class="sb-item active">精选水果</text>
<text class="sb-item" v-for="name in ['肉制品','水产海鲜','米面粮油','厨房主食','新鲜蛋品','调味品','日配冷藏','豆制品']" :key="name">{{name}}</text>
<text class="sb-item active">绮鹃€夋按鏋?/text>
<text class="sb-item" v-for="name in ['鑲夊埗鍝?,'姘翠骇娴烽矞','绫抽潰绮补','鍘ㄦ埧涓婚','鏂伴矞铔嬪搧','璋冨懗鍝?,'鏃ラ厤鍐疯棌','璞嗗埗鍝?]" :key="name">{{name}}</text>
</view>
<view class="main-mock">
<view class="category-section">
<view class="section-title"><text class="st-txt">精选水果</text></view>
<view class="section-title"><text class="st-txt">绮鹃€夋按鏋?/text></view>
<view class="grid-container">
<view class="grid-item" v-for="i in 6" :key="i">
<view class="item-img-box"><text class="item-placeholder">🍐</text></view>
<text class="item-txt">{{ ['精品香蕉','坚果优选','猕猴桃','大肉块','五花肉','鸡腿'][i-1] }}</text>
<view class="item-img-box"><text class="item-placeholder">馃崘</text></view>
<text class="item-txt">{{ ['绮惧搧棣欒晧','鍧氭灉浼橀€?,'鐚曠尨妗?,'澶ц倝鍧?,'浜旇姳鑲?,'楦¤吙'][i-1] }}</text>
</view>
</view>
</view>
<view class="category-section">
<view class="section-title"><text class="st-txt">肉制品</text></view>
<view class="section-title"><text class="st-txt">鑲夊埗鍝?/text></view>
<view class="grid-container">
<view class="grid-item" v-for="i in 3" :key="i">
<view class="item-img-box"><text class="item-placeholder">🥩</text></view>
<text class="item-txt">{{ ['大肉块','五花肉','鸡腿'][i-1] }}</text>
<view class="item-img-box"><text class="item-placeholder">馃ォ</text></view>
<text class="item-txt">{{ ['澶ц倝鍧?,'浜旇姳鑲?,'楦¤吙'][i-1] }}</text>
</view>
</view>
</view>
@@ -60,124 +60,124 @@
</view>
</view>
<view class="phone-tabbar">
<view class="tb-item"><text class="tb-ic">🏠</text></view>
<view class="tb-item active"><text class="tb-ic">📂</text></view>
<view class="tb-item"><text class="tb-ic">🛒</text></view>
<view class="tb-item"><text class="tb-ic">👤</text></view>
<view class="tb-item"><text class="tb-ic">馃彔</text></view>
<view class="tb-item active"><text class="tb-ic">馃搨</text></view>
<view class="tb-item"><text class="tb-ic">馃洅</text></view>
<view class="tb-item"><text class="tb-ic">馃懁</text></view>
</view>
</view>
</view>
<text class="style-name" :style="{color: selectedStyle === 1 ? '#2d8cf0' : '#666'}">样式1</text>
<text class="style-name" :style="{color: selectedStyle === 1 ? '#2d8cf0' : '#666'}">鏍峰紡1</text>
</view>
<!-- 样式2 -->
<!-- 鏍峰紡2 -->
<view class="style-card-wrapper">
<view :class="['style-card', selectedStyle === 2 ? 'active' : '']" @click="selectedStyle = 2">
<view class="phone-mock">
<view class="phone-header-img"></view>
<view class="phone-header-v2">
<text class="p2-title">分类</text>
<view class="home-ic">🏠</view>
<text class="p2-title">鍒嗙被</text>
<view class="home-ic">馃彔</view>
</view>
<view class="phone-body p2-body">
<view class="search-bar-v2">
<text class="ic-search">🔍</text>
<text class="search-ph">点击搜索商品信息</text>
<text class="ic-search">馃攳</text>
<text class="search-ph">鐐瑰嚮鎼滅储鍟嗗搧淇℃伅</text>
</view>
<view class="tabs-v2">
<text class="t2-item active">水果</text>
<text class="t2-item">全部</text>
<text class="t2-item">热带水果</text>
<text class="t2-item">西瓜葡萄</text>
<text class="t2-arrow">▼</text>
<text class="t2-item active">姘存灉</text>
<text class="t2-item">鍏ㄩ儴</text>
<text class="t2-item">鐑甫姘存灉</text>
<text class="t2-item">瑗跨摐钁¤悇</text>
<text class="t2-arrow">鈻?/text>
</view>
<view class="style2-content">
<view class="sidebar-v2">
<text class="s2-item active">乳品</text>
<text class="s2-item">午间零食</text>
<text class="s2-item">新鲜蔬菜</text>
<text class="s2-item">美妆护肤</text>
<text class="s2-item">宠物用品</text>
<text class="s2-item">户外玩具</text>
<text class="s2-item active">涔冲搧</text>
<text class="s2-item">鍗堥棿闆堕</text>
<text class="s2-item">鏂伴矞钄彍</text>
<text class="s2-item">缇庡鎶よ偆</text>
<text class="s2-item">瀹犵墿鐢ㄥ搧</text>
<text class="s2-item">鎴峰鐜╁叿</text>
</view>
<view class="main-v2">
<view class="banner-mock-v2">
<text class="b-txt">深层 V8 高清直屏\n双镜头/VR科技体验</text>
<text class="b-txt">娣卞眰 V8 楂樻竻鐩村睆\n鍙岄暅澶?VR绉戞妧浣撻獙</text>
</view>
<view class="prod-v2" v-for="i in 2" :key="i">
<text class="p-name">Haier/海尔 BCD-216STPT 时尚静音冰箱 三门出门租家用小型电冰箱</text>
<text class="p-name">Haier/娴峰皵 BCD-216STPT 鏃跺皻闈欓煶鍐扮 涓夐棬鍑洪棬绉熷鐢ㄥ皬鍨嬬數鍐扮</text>
<view class="p-price-row">
<text class="p-price">¥999.00</text>
<text class="p-sales">已售 92</text>
<view class="btn-buy"><text class="buy-txt">立即购买</text></view>
<text class="p-price">999.00</text>
<text class="p-sales">宸插敭 92</text>
<view class="btn-buy"><text class="buy-txt">绔嬪嵆璐拱</text></view>
</view>
</view>
</view>
</view>
</view>
<view class="cart-badge">🛒<text class="badge-num">7</text></view>
<view class="cart-badge">馃洅<text class="badge-num">7</text></view>
<view class="footer-p2">
<text class="f2-total">¥999.00</text>
<view class="btn-settle"><text class="settle-txt">去结算</text></view>
<text class="f2-total">999.00</text>
<view class="btn-settle"><text class="settle-txt">鍘荤粨绠?/text></view>
</view>
</view>
</view>
<text class="style-name" :style="{color: selectedStyle === 2 ? '#2d8cf0' : '#666'}">样式2</text>
<text class="style-name" :style="{color: selectedStyle === 2 ? '#2d8cf0' : '#666'}">鏍峰紡2</text>
</view>
<!-- 样式3 -->
<!-- 鏍峰紡3 -->
<view class="style-card-wrapper">
<view :class="['style-card', selectedStyle === 3 ? 'active' : '']" @click="selectedStyle = 3">
<view class="phone-mock">
<view class="phone-header">
<text class="p-title">产品分类</text>
<text class="p-title">浜у搧鍒嗙被</text>
</view>
<view class="phone-body">
<view class="search-bar-v3">
<view class="home-btn">🏠</view>
<view class="home-btn">馃彔</view>
<view class="search-input-v3">
<text class="ic-search">🔍</text>
<text class="search-ph">搜索商品</text>
<text class="ic-search">馃攳</text>
<text class="search-ph">鎼滅储鍟嗗搧</text>
</view>
</view>
<view class="tabs-v3">
<text class="t3-item active">水果</text>
<text class="t3-item specialty">时时生鲜</text>
<text class="t3-item">休闲零食</text>
<text class="t3-item">坚果蜜饯</text>
<text class="t3-arrow"></text>
<text class="t3-item active">姘存灉</text>
<text class="t3-item specialty">鏃舵椂鐢熼矞</text>
<text class="t3-item">浼戦棽闆堕</text>
<text class="t3-item">鍧氭灉铚滈ク</text>
<text class="t3-arrow">鈭?/text>
</view>
<view class="style3-content">
<view class="sidebar-v3">
<text class="s3-item active">乳品</text>
<text class="s3-item">午间零食</text>
<text class="s3-item">新鲜蔬菜</text>
<text class="s3-item">特惠专区</text>
<text class="s3-item">大闸蟹</text>
<text class="s3-item">精选礼盒</text>
<text class="s3-item active">涔冲搧</text>
<text class="s3-item">鍗堥棿闆堕</text>
<text class="s3-item">鏂伴矞钄彍</text>
<text class="s3-item">鐗规儬涓撳尯</text>
<text class="s3-item">澶ч椄锜?/text>
<text class="s3-item">绮鹃€夌ぜ鐩?/text>
</view>
<view class="main-v3">
<view class="prod-v3" v-for="i in 5" :key="i">
<view class="pv-img"></view>
<view class="pv-info">
<text class="pv-name">【橙中爱马仕】果际新骑士晚季甜橙10个单装</text>
<text class="pv-price">¥25.99</text>
<text class="pv-name">銆愭涓埍椹粫銆戞灉闄呮柊楠戝鏅氬鐢滄10涓崟瑁?/text>
<text class="pv-price">25.99</text>
</view>
<view class="pv-add-box">
<text class="pv-add">🛒</text>
<text class="pv-add">馃洅</text>
</view>
</view>
</view>
</view>
</view>
<view class="cart-v3">
<view class="c3-ic-box">🛒<text class="c3-badge">7</text></view>
<text class="c3-price">¥999.00</text>
<view class="btn-settle-v3"><text class="settle-txt">去结算</text></view>
<view class="c3-ic-box">馃洅<text class="c3-badge">7</text></view>
<text class="c3-price">999.00</text>
<view class="btn-settle-v3"><text class="settle-txt">鍘荤粨绠?/text></view>
</view>
</view>
</view>
<text class="style-name" :style="{color: selectedStyle === 3 ? '#2d8cf0' : '#666'}">样式3</text>
<text class="style-name" :style="{color: selectedStyle === 3 ? '#2d8cf0' : '#666'}">鏍峰紡3</text>
</view>
</view>
@@ -192,7 +192,7 @@ const selectedStyle = ref(1)
const handleSave = () => {
console.log('Saving classification style:', selectedStyle.value)
uni.showToast({ title: '保存成功', icon: 'success' })
uni.showToast({ title: '淇濆瓨鎴愬姛', icon: 'success' })
}
const handleReset = () => {
@@ -362,7 +362,7 @@ const handleReset = () => {
.t3-item.specialty { background-color: #f2270c; color: #fff; padding: 2px 8px; border-radius: 10px; }
.t3-arrow { font-size: 12px; color: #ccc; flex: 1; text-align: right; }
.style3-content { flex: 1; display: flex; flex-direction: row; background-color: #fff; }
.style3-content { flex: 1; display: flex; flex-direction: row; }
.sidebar-v3 { width: 75px; background-color: #f7f7f7; }
.s3-item { height: 50px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #666; }
.s3-item.active { background-color: #fff; color: #333; font-weight: bold; position: relative; }
@@ -383,3 +383,4 @@ const handleReset = () => {
.c3-price { font-size: 14px; color: #f2270c; font-weight: bold; flex: 1; }
.btn-settle-v3 { background-color: #f2270c; padding: 6px 20px; border-radius: 20px; }
</style>

View File

@@ -1,35 +1,35 @@
<template>
<template>
<view class="admin-data-config anim-fade-in">
<!-- 顶部标题 -->
<!-- 椤堕儴鏍囬 -->
<view class="page-header border-shadow">
<view class="header-left">
<text class="page-title">数据配置</text>
<text class="page-title">鏁版嵁閰嶇疆</text>
</view>
<view class="header-right">
<view class="btn-save" @click="handleSave">
<text class="save-txt">保存</text>
<text class="save-txt">淇濆瓨</text>
</view>
</view>
</view>
<!-- 主内容区:三栏布局 -->
<!-- 涓诲唴瀹瑰尯锛氫笁鏍忓竷灞€ -->
<view class="main-content">
<view class="card-container border-shadow">
<!-- A. 左栏:配置分类菜单 -->
<!-- A. 宸︽爮锛氶厤缃垎绫昏彍鍗?-->
<MenuSide
:categories="categories"
:activeKey="activeKey"
@change="k => activeKey = k"
/>
<!-- B. 中栏:手机预览 -->
<!-- B. 涓爮锛氭墜鏈洪瑙?-->
<PhonePreview
:activeKey="activeKey"
:activeLabel="activeLabel"
:activeConfig="activeConfig"
/>
<!-- C. 右栏:配置表单 -->
<!-- C. 鍙虫爮锛氶厤缃〃鍗?-->
<view class="settings-column">
<view class="settings-header">
<view class="title-marker"></view>
@@ -39,22 +39,22 @@
<text class="settings-desc">{{ activeCategory?.recommendSizeText }}</text>
</view>
<!-- 开屏广告特有字段 -->
<!-- 寮€灞忓箍鍛婄壒鏈夊瓧娈?-->
<view v-if="activeKey === 'ad'" class="ad-special-fields">
<view class="form-row">
<text class="field-label">开启广告</text>
<text class="field-label">寮€鍚箍鍛?/text>
<switch :checked="activeConfig.enabled" @change="handleSwitchAd" color="#2d8cf0" />
</view>
<view class="form-row">
<text class="field-label">广告时间</text>
<text class="field-label">骞垮憡鏃堕棿</text>
<view class="input-with-unit">
<input type="number" class="time-input" v-model="activeConfig.durationSeconds" />
<text class="unit-txt">单位(秒)</text>
<text class="unit-txt">鍗曚綅(绉?</text>
</view>
</view>
</view>
<!-- 图片项编辑器 -->
<!-- 鍥剧墖椤圭紪杈戝櫒 -->
<view v-if="activeKey !== 'ad' || activeConfig.enabled">
<CarouselEditor
:items="activeConfig.items"
@@ -67,7 +67,7 @@
/>
</view>
<view v-else class="ad-disabled-placeholder">
<text class="disabled-txt">开屏广告已关闭,开启后可配置图片</text>
<text class="disabled-txt">寮€灞忓箍鍛婂凡鍏抽棴锛屽紑鍚悗鍙厤缃浘鐗?/text>
</view>
</view>
</view>
@@ -82,48 +82,48 @@ import MenuSide from '@/pages/mall/admin/decoration/components/MenuSide.uvue'
import PhonePreview from '@/pages/mall/admin/decoration/components/PhonePreview.uvue'
import CarouselEditor from '@/pages/mall/admin/decoration/components/CarouselEditor.uvue'
// 状态定义
// 鐘舵€佸畾涔?
const activeKey = ref('jingpin')
const categories = reactive<Category[]>([
{ key: 'jingpin', label: '首页精品推荐图片', type: 'carousel', recommendSizeText: '建议尺寸690 * 240px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'hot', label: '热门榜单推荐图片', type: 'carousel', recommendSizeText: '建议尺寸690 * 240px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'new', label: '首发新品推荐图片', type: 'carousel', recommendSizeText: '建议尺寸690 * 240px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'promo', label: '促销单品推荐图片', type: 'carousel', recommendSizeText: '建议尺寸690 * 240px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'login', label: '后台登录页面幻灯片', type: 'carousel', recommendSizeText: '建议尺寸690 * 240px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'group', label: '拼团列表轮播图', type: 'carousel', recommendSizeText: '建议尺寸710 * 300px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'points', label: '积分商城轮播图', type: 'carousel', recommendSizeText: '建议尺寸710 * 300px,拖拽图片可调整图片顺序哦,最多添加五张' },
{ key: 'ad', label: '开屏广告', type: 'ad', recommendSizeText: '建议尺寸750 * 1334px,拖拽图片可调整图片顺序哦,最多添加五张' }
{ key: 'jingpin', label: '棣栭〉绮惧搧鎺ㄨ崘鍥剧墖', type: 'carousel', recommendSizeText: '寤鸿灏哄锛?90 * 240px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'hot', label: '鐑棬姒滃崟鎺ㄨ崘鍥剧墖', type: 'carousel', recommendSizeText: '寤鸿灏哄锛?90 * 240px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'new', label: '棣栧彂鏂板搧鎺ㄨ崘鍥剧墖', type: 'carousel', recommendSizeText: '寤鸿灏哄锛?90 * 240px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'promo', label: '淇冮攢鍗曞搧鎺ㄨ崘鍥剧墖', type: 'carousel', recommendSizeText: '寤鸿灏哄锛?90 * 240px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'login', label: '鍚庡彴鐧诲綍椤甸潰骞荤伅鐗?, type: 'carousel', recommendSizeText: '寤鸿灏哄锛?90 * 240px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'group', label: '鎷煎洟鍒楄〃杞挱鍥?, type: 'carousel', recommendSizeText: '寤鸿灏哄锛?10 * 300px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'points', label: '绉垎鍟嗗煄杞挱鍥?, type: 'carousel', recommendSizeText: '寤鸿灏哄锛?10 * 300px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? },
{ key: 'ad', label: '寮€灞忓箍鍛?, type: 'ad', recommendSizeText: '寤鸿灏哄锛?50 * 1334px锛屾嫋鎷藉浘鐗囧彲璋冩暣鍥剧墖椤哄簭鍝︼紝鏈€澶氭坊鍔犱簲寮? }
])
// 初始化数据
// 鍒濆鍖栨暟鎹?
const configMap = reactive<Record<string, ConfigData>>({
'jingpin': { max: 5, items: [{ id: 1, name: '1', imageUrl: '', link: { type: 'internal', value: '/pages/points_mall/integral_index' }, sort: 0 }] },
'hot': { max: 5, items: [{ id: 1, name: '1', imageUrl: '', link: { type: 'internal', value: '/pages/index/index' }, sort: 0 }] },
'new': { max: 5, items: [{ id: 1, name: '1', imageUrl: '', link: { type: 'internal', value: '/pages/index/index' }, sort: 0 }] },
'promo': { max: 5, items: [{ id: 1, name: '1', imageUrl: '', link: { type: 'internal', value: '/pages/points_mall/integral_index' }, sort: 0 }] },
'login': { max: 5, items: [{ id: 1, name: '1', imageUrl: '', link: { type: 'internal', value: '' }, sort: 0 }] },
'group': { max: 5, items: [{ id: 1, name: '拼团', imageUrl: '', link: { type: 'internal', value: '/pages/activity/goods_combination/index' }, sort: 0 }] },
'group': { max: 5, items: [{ id: 1, name: '鎷煎洟', imageUrl: '', link: { type: 'internal', value: '/pages/activity/goods_combination/index' }, sort: 0 }] },
'points': { max: 5, items: [{ id: 1, name: '1', imageUrl: '', link: { type: 'internal', value: '/pages/points_mall/integral_index' }, sort: 0 }] },
'ad': { enabled: false, durationSeconds: 3, max: 5, items: [] }
})
// 计算属性
// 璁$畻灞炴€?
const activeCategory = computed(() => categories.find(c => c.key === activeKey.value))
const activeLabel = computed(() => activeCategory.value?.label ?? '')
const activeConfig = computed(() => configMap[activeKey.value])
const activeTitle = computed(() => {
if (activeKey.value === 'ad') return '引导页设置'
if (activeKey.value === 'login') return '幻灯片设置'
return '轮播图设置'
if (activeKey.value === 'ad') return '寮曞椤佃缃?
if (activeKey.value === 'login') return '骞荤伅鐗囪缃?
return '杞挱鍥捐缃?
})
// 方法
// 鏂规硶
const handleSave = () => {
uni.showLoading({ title: '保存中...' })
uni.showLoading({ title: '淇濆瓨涓?..' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '保存成功', icon: 'success' })
uni.showToast({ title: '淇濆瓨鎴愬姛', icon: 'success' })
}, 800)
}
@@ -134,7 +134,7 @@ const handleSwitchAd = (e: any) => {
const handleAddItem = () => {
const config = activeConfig.value
if (config.items.length >= config.max) {
uni.showToast({ title: `最多添加 ${config.max} 条`, icon: 'none' })
uni.showToast({ title: `鏈€澶氭坊鍔?${config.max} 鏉, icon: 'none' })
return
}
config.items.push({
@@ -167,11 +167,11 @@ const handleUpdateItem = (payload: any) => {
const handleSelectLink = (index: number) => {
uni.showActionSheet({
itemList: ['内部页面', '外部链接', '其他小程序'],
itemList: ['鍐呴儴椤甸潰', '澶栭儴閾炬帴', '鍏朵粬灏忕▼搴?],
success: (res) => {
const types: LinkType[] = ['internal', 'external', 'miniProgram']
activeConfig.value.items[index].link.type = types[res.tapIndex]
uni.showToast({ title: '功能建设中', icon: 'none' })
uni.showToast({ title: '鍔熻兘寤鸿涓?, icon: 'none' })
}
})
}
@@ -219,12 +219,12 @@ const handleSelectLink = (index: number) => {
display: flex;
flex-direction: row;
min-height: 800px;
background-color: #fff;
border-radius: 8px;
overflow: hidden;
}
/* 右侧设置 */
/* 鍙充晶璁剧疆 */
.settings-column {
flex: 1;
padding: 30px;
@@ -243,7 +243,7 @@ const handleSelectLink = (index: number) => {
.settings-desc-box { margin-bottom: 24px; padding-left: 13px; }
.settings-desc { font-size: 13px; color: #999; }
/* 开屏广告特有样式 */
/* 寮€灞忓箍鍛婄壒鏈夋牱寮?*/
.ad-special-fields { padding: 20px; background-color: #f6f8fb; border-radius: 8px; margin-bottom: 20px; }
.form-row { display: flex; flex-direction: row; align-items: center; margin-bottom: 15px; }
.field-label { width: 80px; font-size: 14px; color: #333; }
@@ -257,3 +257,4 @@ const handleSelectLink = (index: number) => {
.anim-fade-in { animation: fadeIn 0.4s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
</style>

View File

@@ -1,23 +1,23 @@
<template>
<template>
<view class="admin-decoration-home">
<view class="content-container">
<!-- 左侧:手机预览区 -->
<!-- 宸︿晶锛氭墜鏈洪瑙堝尯 -->
<view class="preview-section border-shadow">
<view class="phone-mock">
<view class="phone-inner">
<view class="phone-header-img">
<view class="status-bar-mock"></view>
<view class="search-bar-mock">
<text class="search-ic">🔍</text>
<text class="search-ph">请输入搜索词</text>
<text class="search-ic">馃攳</text>
<text class="search-ph">璇疯緭鍏ユ悳绱㈣瘝</text>
</view>
<view class="tabs-mock">
<text class="tab-item active">首页</text>
<text class="tab-item">生活家居</text>
<text class="tab-item">运动专区</text>
<text class="tab-item">电子产品</text>
<text class="tab-item">家用电器</text>
<text class="tab-more">≡</text>
<text class="tab-item active">棣栭〉</text>
<text class="tab-item">鐢熸椿瀹跺眳</text>
<text class="tab-item">杩愬姩涓撳尯</text>
<text class="tab-item">鐢靛瓙浜у搧</text>
<text class="tab-item">瀹剁敤鐢靛櫒</text>
<text class="tab-more">鈮?/text>
</view>
</view>
@@ -43,8 +43,8 @@
<!-- Announcement -->
<view class="notice-mock">
<view class="notice-ic">📢</view>
<text class="notice-txt">CRMEB 年中618活动开启进行中</text>
<view class="notice-ic">馃摙</view>
<text class="notice-txt">CRMEB 骞翠腑618娲诲姩寮€鍚繘琛屼腑锛?/text>
<text class="notice-arr">></text>
</view>
@@ -52,11 +52,11 @@
<view class="checkin-mock">
<view class="checkin-days">
<view class="day-dot" v-for="i in 7" :key="i">
<view class="dot-circle">⭐</view>
<text class="dot-text">周{{ weekDays[i-1] }}</text>
<view class="dot-circle">猸?/view>
<text class="dot-text">鍛▄{ weekDays[i-1] }}</text>
</view>
</view>
<view class="btn-checkin"><text class="check-txt">签到</text></view>
<view class="btn-checkin"><text class="check-txt">绛惧埌</text></view>
</view>
<!-- Bottom Space -->
@@ -66,54 +66,54 @@
<!-- Bottom TabBar -->
<view class="tabbar-mock">
<view class="tb-item active">
<text class="tb-ic">🏠</text>
<text class="tb-txt">首页</text>
<text class="tb-ic">馃彔</text>
<text class="tb-txt">棣栭〉</text>
</view>
<view class="tb-item">
<text class="tb-ic">📂</text>
<text class="tb-txt">分类</text>
<text class="tb-ic">馃搨</text>
<text class="tb-txt">鍒嗙被</text>
</view>
<view class="tb-item">
<text class="tb-ic">🛒</text>
<text class="tb-txt">购物车</text>
<text class="tb-ic">馃洅</text>
<text class="tb-txt">璐墿杞?/text>
</view>
<view class="tb-item">
<text class="tb-ic">👤</text>
<text class="tb-txt">我的</text>
<text class="tb-ic">馃懁</text>
<text class="tb-txt">鎴戠殑</text>
</view>
</view>
</view>
</view>
</view>
<!-- 右侧:列表管理区 -->
<!-- 鍙充晶锛氬垪琛ㄧ鐞嗗尯 -->
<view class="list-section">
<view class="manage-card border-shadow">
<view class="action-bar">
<view class="btn-primary-blue mr-10" @click="handleAdd">
<text class="btn-txt">添加页面</text>
<text class="btn-txt">娣诲姞椤甸潰</text>
</view>
<view class="btn-import-blue" @click="handleImport">
<text class="btn-txt">导入模板</text>
<text class="btn-txt">瀵煎叆妯℃澘</text>
</view>
</view>
<!-- 表格 -->
<!-- 琛ㄦ牸 -->
<view class="table-container">
<view class="table-header-row">
<view class="th" style="width: 80px;">页面ID</view>
<view class="th" style="flex: 2;">模板名称</view>
<view class="th" style="flex: 1;">模板类型</view>
<view class="th" style="flex: 2;">添加时间</view>
<view class="th" style="flex: 2;">更新时间</view>
<view class="th" style="width: 280px;">操作</view>
<view class="th" style="width: 80px;">椤甸潰ID</view>
<view class="th" style="flex: 2;">妯℃澘鍚嶇О</view>
<view class="th" style="flex: 1;">妯℃澘绫诲瀷</view>
<view class="th" style="flex: 2;">娣诲姞鏃堕棿</view>
<view class="th" style="flex: 2;">鏇存柊鏃堕棿</view>
<view class="th" style="width: 280px;">鎿嶄綔</view>
</view>
<view v-for="(item, index) in tableData" :key="index" class="table-body-row">
<view class="td" style="width: 80px;">{{ item.id }}</view>
<view class="td" style="flex: 2;">{{ item.name }}</view>
<view class="td" style="flex: 1;">
<view :class="['type-tag', item.type === '首页' ? 'type-home' : 'type-topic']">
<view :class="['type-tag', item.type === '棣栭〉' ? 'type-home' : 'type-topic']">
<text class="tag-label">{{ item.type }}</text>
</view>
</view>
@@ -121,27 +121,27 @@
<view class="td" style="flex: 2;">{{ item.updateTime }}</view>
<view class="td" style="width: 280px;">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-link" @click="handleEdit(item)">缂栬緫</text>
<text class="op-split">|</text>
<text class="op-link text-danger">删除</text>
<text class="op-link text-danger">鍒犻櫎</text>
<text class="op-split">|</text>
<text class="op-link">预览</text>
<text class="op-link">棰勮</text>
<text class="op-split">|</text>
<text class="op-link" v-if="item.type !== '首页'">设为首页</text>
<text class="op-split" v-if="item.type !== '首页'">|</text>
<text class="op-link">导出模板</text>
<text class="op-link" v-if="item.type !== '棣栭〉'">璁句负棣栭〉</text>
<text class="op-split" v-if="item.type !== '棣栭〉'">|</text>
<text class="op-link">瀵煎嚭妯℃澘</text>
</view>
</view>
</view>
</view>
<!-- 分页器 -->
<!-- 鍒嗛〉鍣?-->
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">{{ total }} 条</text>
<text class="total-txt">鍏?{{ total }} 鏉?/text>
</view>
<view class="page-select">
<text class="page-val">15条/页 ▼</text>
<text class="page-val">15鏉?椤?鈻?/text>
</view>
<view class="page-btns">
<text class="p-btn disabled"><</text>
@@ -149,64 +149,64 @@
<text class="p-btn">></text>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<text class="jump-txt">鍓嶅線</text>
<input class="jump-input" value="1" />
<text class="jump-txt">页</text>
<text class="jump-txt">椤?/text>
</view>
</view>
</view>
</view>
</view>
<!-- 添加页面侧边栏 -->
<!-- 娣诲姞椤甸潰渚ц竟鏍?-->
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
<view class="drawer-header">
<text class="title-txt">添加页面</text>
<text class="close-btn" @click="closeDrawer">×</text>
<text class="title-txt">娣诲姞椤甸潰</text>
<text class="close-btn" @click="closeDrawer"></text>
</view>
<scroll-view class="drawer-body" :scroll-y="true">
<view class="form-item-v">
<text class="v-label">页面名称</text>
<input class="v-input" v-model="formName" placeholder="请填写页面名称" />
<text class="v-label">椤甸潰鍚嶇О</text>
<input class="v-input" v-model="formName" placeholder="璇峰~鍐欓〉闈㈠悕绉? />
</view>
<view class="form-item-v">
<text class="v-label">页面类型</text>
<text class="v-label">椤甸潰绫诲瀷</text>
<view class="radio-group">
<view class="radio-item" @click="formType = '首页'">
<view :class="['radio-dot', formType === '首页' ? 'active' : '']"></view>
<text class="radio-txt">首页</text>
<view class="radio-item" @click="formType = '棣栭〉'">
<view :class="['radio-dot', formType === '棣栭〉' ? 'active' : '']"></view>
<text class="radio-txt">棣栭〉</text>
</view>
<view class="radio-item" @click="formType = '专题页'">
<view :class="['radio-dot', formType === '专题页' ? 'active' : '']"></view>
<text class="radio-txt">专题页</text>
<view class="radio-item" @click="formType = '涓撻椤?">
<view :class="['radio-dot', formType === '涓撻椤? ? 'active' : '']"></view>
<text class="radio-txt">涓撻椤?/text>
</view>
</view>
</view>
<view class="template-select-title">
<text class="t-title">选择模板</text>
<text class="t-sub">请选择要引用的模板</text>
<text class="t-title">閫夋嫨妯℃澘</text>
<text class="t-sub">璇烽€夋嫨瑕佸紩鐢ㄧ殑妯℃澘</text>
</view>
<view class="template-grid">
<view class="tpl-item" v-for="i in 4" :key="i">
<view class="tpl-thumb">
<text class="tpl-ic">📄</text>
<text class="tpl-ic">馃搫</text>
</view>
<text class="tpl-name">通用模板 {{ i }}</text>
<text class="tpl-name">閫氱敤妯℃澘 {{ i }}</text>
</view>
</view>
</scroll-view>
<view class="drawer-footer">
<view class="btn-cancel" @click="closeDrawer">
<text class="btn-cancel-txt">取消</text>
<text class="btn-cancel-txt">鍙栨秷</text>
</view>
<view class="btn-save" @click="handleSavePage">
<text class="btn-save-txt">提交</text>
<text class="btn-save-txt">鎻愪氦</text>
</view>
</view>
</view>
@@ -217,28 +217,28 @@
<script setup lang="uts">
import { ref } from 'vue'
const menuNames = ['秒杀活动', '商品分类', '拼团活动', '积分商城', '砍价中心', '行业资讯', '我的地址', '积分抽奖', '我的账户', '订单列表']
const weekDays = ['', '二', '三', '四', '五', '六', '日']
const menuNames = ['绉掓潃娲诲姩', '鍟嗗搧鍒嗙被', '鎷煎洟娲诲姩', '绉垎鍟嗗煄', '鐮嶄环涓績', '琛屼笟璧勮', '鎴戠殑鍦板潃', '绉垎鎶藉', '鎴戠殑璐︽埛', '璁㈠崟鍒楄〃']
const weekDays = ['涓€', '浜?, '涓?, '鍥?, '浜?, '鍏?, '鏃?]
const total = ref(11)
const tableData = ref([
{ id: 497, name: 'DIY导入数据', type: '专题页', addTime: '2025-03-20 15:18:01', updateTime: '2025-05-21 10:17:45' },
{ id: 496, name: 'DIY导入数据', type: '专题页', addTime: '2025-03-20 15:12:58', updateTime: '2025-03-20 15:12:58' },
{ id: 494, name: '图书类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:42:08', updateTime: '2025-03-19 10:40:13' },
{ id: 493, name: '健康类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:40:55', updateTime: '2025-03-07 09:46:14' },
{ id: 492, name: '演出类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:33:09', updateTime: '2025-03-07 09:49:43' },
{ id: 491, name: '潮玩类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:31:28', updateTime: '2025-03-07 09:55:53' },
{ id: 490, name: '家居类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:30:21', updateTime: '2025-03-07 09:57:59' },
{ id: 482, name: '文具类模板,勿动!!', type: '专题页', addTime: '2025-02-26 11:32:07', updateTime: '2025-03-07 09:59:25' },
{ id: 481, name: '模板', type: '专题页', addTime: '2025-02-26 09:21:04', updateTime: '2025-03-12 14:55:46' },
{ id: 480, name: '模板', type: '专题页', addTime: '2025-02-26 09:19:24', updateTime: '2026-02-02 17:11:45' },
{ id: 479, name: '首页模板,勿动!!', type: '首页', addTime: '2025-02-25 20:59:59', updateTime: '2026-01-20 11:16:20' }
{ id: 497, name: 'DIY瀵煎叆鏁版嵁', type: '涓撻椤?, addTime: '2025-03-20 15:18:01', updateTime: '2025-05-21 10:17:45' },
{ id: 496, name: 'DIY瀵煎叆鏁版嵁', type: '涓撻椤?, addTime: '2025-03-20 15:12:58', updateTime: '2025-03-20 15:12:58' },
{ id: 494, name: '鍥句功绫绘ā鏉匡紝鍕垮姩锛侊紒', type: '涓撻椤?, addTime: '2025-02-27 15:42:08', updateTime: '2025-03-19 10:40:13' },
{ id: 493, name: '鍋ュ悍绫绘ā鏉匡紝鍕垮姩锛侊紒', type: '涓撻椤?, addTime: '2025-02-27 15:40:55', updateTime: '2025-03-07 09:46:14' },
{ id: 492, name: '婕斿嚭绫绘ā鏉匡紝鍕垮姩锛侊紒', type: '涓撻椤?, addTime: '2025-02-27 15:33:09', updateTime: '2025-03-07 09:49:43' },
{ id: 491, name: '娼帺绫绘ā鏉匡紝鍕垮姩锛侊紒', type: '涓撻椤?, addTime: '2025-02-27 15:31:28', updateTime: '2025-03-07 09:55:53' },
{ id: 490, name: '瀹跺眳绫绘ā鏉匡紝鍕垮姩锛侊紒', type: '涓撻椤?, addTime: '2025-02-27 15:30:21', updateTime: '2025-03-07 09:57:59' },
{ id: 482, name: '鏂囧叿绫绘ā鏉匡紝鍕垮姩锛侊紒', type: '涓撻椤?, addTime: '2025-02-26 11:32:07', updateTime: '2025-03-07 09:59:25' },
{ id: 481, name: '妯℃澘', type: '涓撻椤?, addTime: '2025-02-26 09:21:04', updateTime: '2025-03-12 14:55:46' },
{ id: 480, name: '妯℃澘', type: '涓撻椤?, addTime: '2025-02-26 09:19:24', updateTime: '2026-02-02 17:11:45' },
{ id: 479, name: '棣栭〉妯℃澘锛屽嬁鍔紒锛?, type: '棣栭〉', addTime: '2025-02-25 20:59:59', updateTime: '2026-01-20 11:16:20' }
])
const showDrawer = ref(false)
const isClosing = ref(false)
const formName = ref('')
const formType = ref('首页')
const formType = ref('棣栭〉')
const viewState = ref('list') // 'list' | 'design'
const editingName = ref('')
@@ -293,7 +293,7 @@ const handleSaveDesign = () => {
gap: 20px;
}
/* 左侧手机预览区 */
/* 宸︿晶鎵嬫満棰勮鍖?*/
.preview-section {
width: 380px;
height: 800px;
@@ -455,7 +455,7 @@ const handleSaveDesign = () => {
.tb-txt { font-size: 11px; color: #999; }
.tb-item.active .tb-txt { color: #f2270c; }
/* 右侧列表管理区 */
/* 鍙充晶鍒楄〃绠$悊鍖?*/
.list-section { flex: 1; }
.manage-card { display: flex; flex-direction: column; min-height: 800px; }
@@ -614,7 +614,7 @@ const handleSaveDesign = () => {
.drawer-content {
width: 450px;
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
animation: slideIn 0.3s ease-out;
@@ -723,3 +723,4 @@ const handleSaveDesign = () => {
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<template>
<AdminLayout :currentPage="currentPage">
<view class="design-container">
<view class="module-header">
<text class="module-title">模板库</text>
<text class="module-desc">从丰富的模板中快速创建页面</text>
<text class="module-title">妯℃澘搴?/text>
<text class="module-desc">浠庝赴瀵岀殑妯℃澘涓揩閫熷垱寤洪〉闈?/text>
</view>
<view class="templates-grid">
<view v-for="template in templateLibrary" :key="template.id" class="template-card">
@@ -12,7 +12,7 @@
</view>
<view class="template-body">
<text class="template-desc">{{ template.description }}</text>
<button class="btn-use" @click="handleUseTemplate(template.id)">使用模板</button>
<button class="btn-use" @click="handleUseTemplate(template.id)">浣跨敤妯℃澘</button>
</view>
</view>
</view>
@@ -29,30 +29,30 @@ const currentPage = ref<string>('design-templates')
const templateLibrary = ref<any[]>([
{
id: 1,
name: '首页模板A',
description: '经典电商首页布局'
name: '棣栭〉妯℃澘A',
description: '缁忓吀鐢靛晢棣栭〉甯冨眬'
},
{
id: 2,
name: '首页模板B',
description: '简约风格的商城页面'
name: '棣栭〉妯℃澘B',
description: '绠€绾﹂鏍肩殑鍟嗗煄椤甸潰'
},
{
id: 3,
name: '活动模板',
description: '活动促销页面布局'
name: '娲诲姩妯℃澘',
description: '娲诲姩淇冮攢椤甸潰甯冨眬'
},
{
id: 4,
name: '商品模板',
description: '商品展示页面布局'
name: '鍟嗗搧妯℃澘',
description: '鍟嗗搧灞曠ず椤甸潰甯冨眬'
}
])
const handleUseTemplate = (templateId: number) => {
console.log('使用模板', templateId)
console.log('浣跨敤妯℃澘', templateId)
uni.showToast({
title: '使用模板成功',
title: '浣跨敤妯℃澘鎴愬姛',
icon: 'none'
})
}
@@ -62,7 +62,7 @@ const handleUseTemplate = (templateId: number) => {
@import '@/uni.scss';
.design-container {
min-height: 100vh;
background: $background-secondary;
padding: $space-lg;
}
@@ -160,3 +160,4 @@ const handleUseTemplate = (templateId: number) => {
</style>

View File

@@ -1,12 +1,12 @@
<template>
<template>
<view class="admin-main">
<!-- 头部操作区 -->
<!-- 澶撮儴鎿嶄綔鍖?-->
<view class="header-container">
<text class="page-title">主题风格</text>
<button class="save-btn" type="primary" size="mini" @click="handleSave">保存</button>
<text class="page-title">涓婚椋庢牸</text>
<button class="save-btn" type="primary" size="mini" @click="handleSave">淇濆瓨</button>
</view>
<!-- 选项卡/卡片容器 -->
<!-- 閫夐」鍗?鍗$墖瀹瑰櫒 -->
<view class="card-container selection-area">
<view class="theme-list">
<view
@@ -22,11 +22,11 @@
</view>
</view>
<!-- 预览区 -->
<!-- 棰勮鍖?-->
<view class="preview-section">
<!-- 预览 1: 个人中心 -->
<!-- 棰勮 1: 涓汉涓績 -->
<view class="preview-card">
<text class="p-title">个人中心</text>
<text class="p-title">涓汉涓績</text>
<view class="mock-phone">
<view class="mock-status-bar"></view>
<view class="mock-content user-center">
@@ -35,69 +35,69 @@
<view class="mock-avatar"></view>
<view class="user-meta">
<view class="name-line">
<text class="name">我的名字我的名字</text>
<text class="name">鎴戠殑鍚嶅瓧鎴戠殑鍚嶅瓧</text>
<view class="vip-badge">SVIP</view>
</view>
<text class="user-id">ID: 3659884 ></text>
</view>
<view class="settings-icons">
<text class="ic">🔔</text>
<text class="ic">⚙️</text>
<text class="ic">馃敂</text>
<text class="ic">鈿欙笍</text>
</view>
</view>
<view class="stats-row">
<view class="stat-item"><text class="val">0.00</text><text class="lab">余额</text></view>
<view class="stat-item"><text class="val">20</text><text class="lab">积分</text></view>
<view class="stat-item"><text class="val">25</text><text class="lab">优惠券</text></view>
<view class="stat-item"><text class="val">0.00</text><text class="lab">浣欓</text></view>
<view class="stat-item"><text class="val">20</text><text class="lab">绉垎</text></view>
<view class="stat-item"><text class="val">25</text><text class="lab">浼樻儬鍒?/text></view>
</view>
</view>
<view class="vip-card-banner">
<view class="vip-left">
<text class="vip-l-t1">会员可享多项权益</text>
<text class="vip-l-t2">会员剩余434天</text>
<text class="vip-l-t1">浼氬憳鍙韩澶氶」鏉冪泭</text>
<text class="vip-l-t2">浼氬憳鍓╀綑434澶?/text>
</view>
<view class="btn-vip">立即续费</view>
<view class="btn-vip">绔嬪嵆缁垂</view>
</view>
<view class="order-section">
<view class="o-title"><text>订单中心</text><text class="more">查看全部 ></text></view>
<view class="o-title"><text>璁㈠崟涓績</text><text class="more">鏌ョ湅鍏ㄩ儴 ></text></view>
<view class="o-icons">
<view class="o-item">📦<text>待付款</text></view>
<view class="o-item">🚚<text>待发货</text></view>
<view class="o-item">🎁<text>待收货</text></view>
<view class="o-item">⭐<text>待评价</text></view>
<view class="o-item">🔄<text>售后/退款</text></view>
<view class="o-item">馃摝<text>寰呬粯娆?/text></view>
<view class="o-item">馃殮<text>寰呭彂璐?/text></view>
<view class="o-item">馃巵<text>寰呮敹璐?/text></view>
<view class="o-item">猸?text>寰呰瘎浠?/text></view>
<view class="o-item">馃攧<text>鍞悗/閫€娆?/text></view>
</view>
</view>
<view class="invite-banner">
<text class="i-t1">邀请好友赚佣金</text>
<text class="i-t2">推广好友注册</text>
<text class="i-t1">閭€璇峰ソ鍙嬭禋浣i噾</text>
<text class="i-t2">鎺ㄥ箍濂藉弸娉ㄥ唽</text>
</view>
<view class="service-section">
<view class="s-title">我的服务</view>
<view class="s-title">鎴戠殑鏈嶅姟</view>
<view class="s-grid">
<view class="s-item">👤<text>会员中心</text></view>
<view class="s-item">📢<text>我的推广</text></view>
<view class="s-item">📅<text>签到</text></view>
<view class="s-item">🎫<text>优惠券</text></view>
<view class="s-item">馃懁<text>浼氬憳涓績</text></view>
<view class="s-item">馃摙<text>鎴戠殑鎺ㄥ箍</text></view>
<view class="s-item">馃搮<text>绛惧埌</text></view>
<view class="s-item">馃帿<text>浼樻儬鍒?/text></view>
</view>
</view>
<view class="mock-tabbar">
<view class="t-item">🏠<text>首页</text></view>
<view class="t-item">🔍<text>分类</text></view>
<view class="t-item">🛒<text>购物车</text></view>
<view class="t-item active" :style="{ color: currentThemeColor }">👤<text>我的</text></view>
<view class="t-item">馃彔<text>棣栭〉</text></view>
<view class="t-item">馃攳<text>鍒嗙被</text></view>
<view class="t-item">馃洅<text>璐墿杞?/text></view>
<view class="t-item active" :style="{ color: currentThemeColor }">馃懁<text>鎴戠殑</text></view>
</view>
</view>
</view>
</view>
<!-- 预览 2: 商品详情 -->
<!-- 棰勮 2: 鍟嗗搧璇︽儏 -->
<view class="preview-card">
<text class="p-title">商品详情</text>
<text class="p-title">鍟嗗搧璇︽儏</text>
<view class="mock-phone">
<view class="mock-status-bar"></view>
<view class="mock-content product-detail">
@@ -106,60 +106,60 @@
</view>
<view class="p-main-info">
<view class="p-price-row">
<text class="p-symbol" :style="{ color: currentThemeColor }">¥</text>
<text class="p-symbol" :style="{ color: currentThemeColor }"></text>
<text class="p-price" :style="{ color: currentThemeColor }">199.00</text>
<text class="p-old-price">¥ 100.00</text>
<text class="p-old-price"> 100.00</text>
<view class="p-tag-svip">SVIP</view>
</view>
<text class="p-name">企鹅针织条纹四件套新款上市性价比高</text>
<text class="p-name">浼侀箙閽堢粐鏉$汗鍥涗欢濂楁柊娆句笂甯傛€т环姣旈珮</text>
<view class="p-stats">
<text>原价: ¥ 234.00</text>
<text>累计销量: 2999999件</text>
<text>库存: 1452件</text>
<text>鍘熶环: 234.00</text>
<text>绱閿€閲? 2999999浠?/text>
<text>搴撳瓨: 1452浠?/text>
</view>
</view>
<view class="p-options">
<view class="opt-row"><text class="opt-lab">优惠券:</text><view class="tags"><text class="t-red">满100减30</text><text class="t-red">满100减30</text></view><text class="more">></text></view>
<view class="opt-row"><text class="opt-lab">活动:</text><view class="tags"><text class="t-action" :style="{ backgroundColor: currentThemeColor }">参与拼团</text><text class="t-action" :style="{ backgroundColor: currentThemeColor }">参与砍价</text><text class="t-action" :style="{ backgroundColor: currentThemeColor }">参与秒杀</text></view><text class="more">></text></view>
<view class="opt-row"><text class="opt-lab">浼樻儬鍒?</text><view class="tags"><text class="t-red">婊?00鍑?0</text><text class="t-red">婊?00鍑?0</text></view><text class="more">></text></view>
<view class="opt-row"><text class="opt-lab">娲诲姩:</text><view class="tags"><text class="t-action" :style="{ backgroundColor: currentThemeColor }">鍙備笌鎷煎洟</text><text class="t-action" :style="{ backgroundColor: currentThemeColor }">鍙備笌鐮嶄环</text><text class="t-action" :style="{ backgroundColor: currentThemeColor }">鍙備笌绉掓潃</text></view><text class="more">></text></view>
</view>
<view class="p-footer">
<view class="f-icons">
<view class="fi"><text>💬</text><text>客服</text></view>
<view class="fi"><text>⭐</text><text>收藏</text></view>
<view class="fi"><text>🛒</text><text>购物车</text></view>
<view class="fi"><text>馃挰</text><text>瀹㈡湇</text></view>
<view class="fi"><text>猸?/text><text>鏀惰棌</text></view>
<view class="fi"><text>馃洅</text><text>璐墿杞?/text></view>
</view>
<view class="f-btns">
<view class="f-btn cart" :style="{ backgroundColor: currentThemeColor, opacity: 0.7 }">加入购物车</view>
<view class="f-btn buy" :style="{ backgroundColor: currentThemeColor }">立即购买</view>
<view class="f-btn cart" :style="{ backgroundColor: currentThemeColor, opacity: 0.7 }">鍔犲叆璐墿杞?/view>
<view class="f-btn buy" :style="{ backgroundColor: currentThemeColor }">绔嬪嵆璐拱</view>
</view>
</view>
</view>
</view>
</view>
<!-- 预览 3: 拼团列表 -->
<!-- 棰勮 3: 鎷煎洟鍒楄〃 -->
<view class="preview-card">
<text class="p-title">拼团列表</text>
<text class="p-title">鎷煎洟鍒楄〃</text>
<view class="mock-phone">
<view class="mock-status-bar"></view>
<view class="mock-content group-list">
<view class="g-header" :style="{ backgroundColor: currentThemeColor }">
<text class="g-h-title">拼团列表</text>
<text class="g-h-title">鎷煎洟鍒楄〃</text>
<view class="g-participation">
<view class="g-avatars"></view>
<text>9999人参与</text>
<text>9999浜哄弬涓?/text>
</view>
</view>
<view class="g-item" v-for="i in 4" :key="i">
<view class="g-img"></view>
<view class="g-info">
<text class="g-name">2021年新款吊灯简约现代大气家用客厅灯北欧风格餐厅卧...</text>
<text class="g-name">2021骞存柊娆惧悐鐏畝绾︾幇浠eぇ姘斿鐢ㄥ鍘呯伅鍖楁椋庢牸椁愬巺鍗?..</text>
<view class="g-bottom">
<view class="g-prices">
<text class="g-p-old">¥ 199.00</text>
<text class="g-p-now" :style="{ color: currentThemeColor }">¥ 124.00</text>
<text class="g-p-old"> 199.00</text>
<text class="g-p-now" :style="{ color: currentThemeColor }"> 124.00</text>
</view>
<view class="g-btn" :style="{ backgroundColor: currentThemeColor }">{{ i % 2 === 0 ? '去拼团' : '已售罄' }}</view>
<view class="g-btn" :style="{ backgroundColor: currentThemeColor }">{{ i % 2 === 0 ? '鍘绘嫾鍥? : '宸插敭缃? }}</view>
</view>
</view>
</view>
@@ -180,11 +180,11 @@ interface ThemeOption {
}
const themeOptions = ref<ThemeOption[]>([
{ id: 'blue', name: '天空蓝', color: '#1890ff' },
{ id: 'green', name: '生鲜绿', color: '#52c41a' },
{ id: 'red', name: '热情红', color: '#e93323' },
{ id: 'pink', name: '魅力粉', color: '#ff4d9f' },
{ id: 'orange', name: '活力橙', color: '#ff8c00' }
{ id: 'blue', name: '澶╃┖钃?, color: '#1890ff' },
{ id: 'green', name: '鐢熼矞缁?, color: '#52c41a' },
{ id: 'red', name: '鐑儏绾?, color: '#e93323' },
{ id: 'pink', name: '榄呭姏绮?, color: '#ff4d9f' },
{ id: 'orange', name: '娲诲姏姗?, color: '#ff8c00' }
])
const selectedThemeId = ref('red')
@@ -196,7 +196,7 @@ const currentThemeColor = computed(() : string => {
const handleSave = () => {
uni.showToast({
title: '保存成功',
title: '淇濆瓨鎴愬姛',
icon: 'success'
})
}
@@ -212,7 +212,7 @@ const handleSave = () => {
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: #fff;
padding: 10px 20px;
border-bottom: 1px solid #f0f0f0;
}
@@ -390,4 +390,4 @@ const handleSave = () => {
.g-h-title { color: #fff; font-size: 16px; font-weight: bold; display: block; margin-bottom: 10px; }
.g-item { background: #fff; border-radius: 8px; margin: 12px; padding: 12px; display: flex; flex-direction: row; }
.g-img { width: 90px; height: 90px; background: #eee; border-radius: 4px; margin-right: 12px; }
</style>
</style>

View File

@@ -0,0 +1,107 @@
<template>
<view class="admin-page">
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">鎼滅储锛?/text>
<input class="filter-input" placeholder="璇疯緭鍏ュ鍚嶃€乁ID" />
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">鏌ヨ</button>
</view>
</view>
</view>
<view class="content-card">
<view class="action-bar">
<button class="btn primary small" @click="onAdd">娣诲姞浠g悊鍟?/button>
</view>
<view class="table-container">
<view class="table-header">
<view class="col col-uid"><text>鐢ㄦ埛UID</text></view>
<view class="col col-avatar"><text>澶村儚</text></view>
<view class="col col-name"><text>鍚嶇О</text></view>
<view class="col col-ratio"><text>鍒嗛攢姣斾緥</text></view>
<view class="col col-count"><text>鍛樺伐鏁伴噺</text></view>
<view class="col col-time"><text>鎴鏃堕棿</text></view>
<view class="col col-status"><text>鐘舵€?/text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<view class="table-body">
<view v-for="item in agentList" :key="item.uid" class="table-row">
<view class="col col-uid"><text>{{ item.uid }}</text></view>
<view class="col col-avatar">
<image class="avatar-img" src="/static/logo.png" mode="aspectFill" />
</view>
<view class="col col-name"><text>{{ item.name }}</text></view>
<view class="col col-ratio"><text>{{ item.ratio }}%</text></view>
<view class="col col-count"><text>{{ item.staffCount }}</text></view>
<view class="col col-time"><text>{{ item.endTime }}</text></view>
<view class="col col-status">
<switch :checked="item.status" color="#1890ff" scale="0.8" />
</view>
<view class="col col-ops">
<text class="op-link">娣诲姞鍛樺伐</text>
<text class="op-divider">|</text>
<text class="op-link">鏌ョ湅鍛樺伐</text>
<text class="op-divider">|</text>
<text class="op-link">缂栬緫</text>
<text class="op-divider">|</text>
<text class="op-link">鍒犻櫎</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const agentList = ref([
{ uid: '60569', name: 'cs2020', ratio: 50, staffCount: 1, endTime: '1970-01-01', status: true },
{ uid: '60571', name: 'www', ratio: 19, staffCount: 1, endTime: '1970-01-01', status: true },
{ uid: '60665', name: 'geyun', ratio: 15, staffCount: 1, endTime: '1970-01-01', status: true },
{ uid: '60678', name: '鏍间簯', ratio: 0, staffCount: 0, endTime: '1970-01-01', status: true }
])
function onSearch() {
uni.showToast({ title: '鏌ヨ涓?..', icon: 'none' })
}
function onAdd() {
uni.showToast({ title: '娣诲姞浠g悊鍟?, icon: 'none' })
}
</script>
<style scoped lang="scss">
.admin-page { /* padding removed */ }
.filter-card { background: #fff; padding: 24px; margin-bottom: 16px; border-radius: 4px; }
.filter-row { display: flex; flex-direction: row; align-items: center; gap: 24px; }
.label { font-size: 14px; color: #333; }
.filter-input { border: 1px solid #d9d9d9; height: 32px; width: 220px; padding: 0 12px; font-size: 14px; }
.btn { height: 32px; padding: 0 16px; font-size: 14px; border: 1px solid #d9d9d9; background: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.btn.primary { background: #1890ff; border-color: #1890ff; color: #fff; }
.content-card { background: #fff; border-radius: 4px; }
.action-bar { padding: 16px 24px; }
.table-container { padding: 0 24px 24px; }
.table-header { display: flex; flex-direction: row; background: #f8faff; border-bottom: 1px solid #f0f0f0; padding: 12px 0; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; padding: 12px 0; align-items: center; &:hover { background: #fafafa; } }
.col { padding: 0 8px; font-size: 14px; color: #333; display: flex; align-items: center; }
.col-uid { width: 80px; }
.col-avatar { width: 80px; justify-content: center; }
.col-name { width: 120px; }
.col-ratio { width: 100px; justify-content: center; }
.col-count { width: 100px; justify-content: center; }
.col-time { width: 120px; justify-content: center; }
.col-status { width: 80px; justify-content: center; }
.col-ops { flex: 1; justify-content: flex-end; }
.avatar-img { width: 32px; height: 32px; border-radius: 2px; }
.op-link { color: #1890ff; cursor: pointer; }
.op-divider { color: #e8e8e8; margin: 0 8px; }
</style>

View File

@@ -0,0 +1,117 @@
<template>
<view class="admin-page">
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">鎼滅储锛?/text>
<input class="filter-input" placeholder="璇疯緭鍏ュ鍚嶃€乁ID" />
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">鏌ヨ</button>
</view>
</view>
</view>
<view class="content-card">
<view class="tabs-row">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === index }"
@click="activeTab = index"
>
<text>{{ tab }}</text>
</view>
</view>
<view class="table-container">
<view class="table-header">
<view class="col col-uid"><text>鐢ㄦ埛UID</text></view>
<view class="col col-name"><text>浠g悊鍟嗗悕绉?/text></view>
<view class="col col-phone"><text>浠g悊鍟嗙數璇?/text></view>
<view class="col col-dept"><text>浜嬩笟閮ㄥ悕绉?/text></view>
<view class="col col-img"><text>鐢宠鍥剧墖</text></view>
<view class="col col-time"><text>鐢宠鏃堕棿</text></view>
<view class="col col-status"><text>鐢宠鐘舵€?/text></view>
<view class="col col-code"><text>閭€璇风爜</text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<view class="table-body">
<view v-for="item in applyList" :key="item.uid" class="table-row">
<view class="col col-uid"><text>{{ item.uid }}</text></view>
<view class="col col-name"><text>{{ item.name }}</text></view>
<view class="col col-phone"><text>{{ item.phone }}</text></view>
<view class="col col-dept"><text>{{ item.deptName }}</text></view>
<view class="col col-img">
<image class="table-img" src="/static/logo.png" mode="aspectFill" />
</view>
<view class="col col-time"><text>{{ item.time }}</text></view>
<view class="col col-status">
<view class="status-tag"><text>{{ item.statusText }}</text></view>
</view>
<view class="col col-code"><view class="code-box"><text>{{ item.code }}</text></view></view>
<view class="col col-ops">
<text class="op-link">鍚屾剰</text>
<text class="op-divider">|</text>
<text class="op-link">鎷掔粷</text>
<text class="op-divider">|</text>
<text class="op-link">鍒犻櫎</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const activeTab = ref(0)
const tabs = ['鍏ㄩ儴', '鐢宠涓?, '宸插悓鎰?, '宸叉嫆缁?]
const applyList = ref([
{ uid: '81806', name: '娴嬭瘯娴嬭瘯', phone: '19910205954', deptName: '26991', time: '2026-01-08 15:30:39', statusText: '鐢宠涓?, code: '70623142' },
{ uid: '82072', name: 'testttt', phone: '18613860515', deptName: '鍑岃景绉戞妧', time: '2026-01-05 12:06:39', statusText: '鐢宠涓?, code: '80889444' },
{ uid: '80586', name: '鐔?, phone: '13759402576', deptName: '鍑岃景绉戞妧', time: '2025-12-06 18:05:18', statusText: '鐢宠涓?, code: '80889444' }
])
function onSearch() {
uni.showToast({ title: '鏌ヨ涓?..', icon: 'none' })
}
</script>
<style scoped lang="scss">
.admin-page { /* padding removed */ }
.filter-card { background: #fff; padding: 24px; margin-bottom: 16px; border-radius: 4px; }
.filter-row { display: flex; flex-direction: row; align-items: center; gap: 24px; }
.label { font-size: 14px; color: #333; }
.filter-input { border: 1px solid #d9d9d9; height: 32px; width: 220px; padding: 0 12px; font-size: 14px; }
.btn { height: 32px; padding: 0 16px; font-size: 14px; border: 1px solid #d9d9d9; background: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.btn.primary { background: #1890ff; border-color: #1890ff; color: #fff; }
.content-card { background: #fff; border-radius: 4px; }
.tabs-row { display: flex; flex-direction: row; padding: 0 24px; border-bottom: 1px solid #f0f0f0; }
.tab-item { padding: 16px 20px; cursor: pointer; position: relative; text { font-size: 15px; color: #666; } &.active { text { color: #1890ff; font-weight: 500; } &::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: #1890ff; } } }
.table-container { padding: 24px; }
.table-header { display: flex; flex-direction: row; background: #f8faff; border-bottom: 1px solid #f0f0f0; padding: 12px 0; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; padding: 12px 0; align-items: center; &:hover { background: #fafafa; } }
.col { padding: 0 8px; font-size: 14px; color: #333; display: flex; align-items: center; }
.col-uid { width: 80px; }
.col-name { width: 120px; }
.col-phone { width: 120px; }
.col-dept { width: 120px; }
.col-img { width: 80px; justify-content: center; }
.col-time { width: 160px; justify-content: center; }
.col-status { width: 100px; justify-content: center; }
.col-code { width: 100px; justify-content: center; }
.col-ops { flex: 1; justify-content: flex-end; }
.table-img { width: 32px; height: 32px; border-radius: 2px; }
.status-tag { border: 1px solid #1890ff; color: #1890ff; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
.code-box { border: 1px solid #d9d9d9; padding: 2px 8px; border-radius: 4px; font-size: 12px; background: #fafafa; }
.op-link { color: #1890ff; cursor: pointer; }
.op-divider { color: #e8e8e8; margin: 0 8px; }
</style>

View File

@@ -0,0 +1,110 @@
<template>
<view class="admin-page">
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">鎼滅储锛?/text>
<input class="filter-input" placeholder="璇疯緭鍏ュ鍚嶃€乁ID" />
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">鏌ヨ</button>
</view>
</view>
</view>
<view class="content-card">
<view class="action-bar">
<button class="btn primary small" @click="onAdd">娣诲姞浜嬩笟閮?/button>
</view>
<view class="table-container">
<view class="table-header">
<view class="col col-uid"><text>鐢ㄦ埛UID</text></view>
<view class="col col-avatar"><text>澶村儚</text></view>
<view class="col col-name"><text>鍚嶇О</text></view>
<view class="col col-code"><text>閭€璇风爜</text></view>
<view class="col col-ratio"><text>鍒嗛攢姣斾緥</text></view>
<view class="col col-count"><text>浠g悊鍟嗘暟閲?/text></view>
<view class="col col-time"><text>鎴鏃堕棿</text></view>
<view class="col col-status"><text>鐘舵€?/text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<view class="table-body">
<view v-for="item in divisionList" :key="item.uid" class="table-row">
<view class="col col-uid"><text>{{ item.uid }}</text></view>
<view class="col col-avatar">
<image class="avatar-img" src="/static/logo.png" mode="aspectFill" />
</view>
<view class="col col-name"><text>{{ item.name }}</text></view>
<view class="col col-code"><text>{{ item.code }}</text></view>
<view class="col col-ratio"><text>{{ item.ratio }}%</text></view>
<view class="col col-count"><text>{{ item.agentCount }}</text></view>
<view class="col col-time"><text>{{ item.endTime }}</text></view>
<view class="col col-status">
<switch :checked="item.status" color="#1890ff" scale="0.8" />
</view>
<view class="col col-ops">
<text class="op-link">鏌ョ湅浠g悊鍟?/text>
<text class="op-divider">|</text>
<text class="op-link">缂栬緫</text>
<text class="op-divider">|</text>
<text class="op-link">鍒犻櫎</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const divisionList = ref([
{ uid: '26991', name: '26991', code: '70623142', ratio: 40, agentCount: 1, endTime: '2026-12-31', status: true },
{ uid: '37221', name: '鍑岃景绉戞妧', code: '80889444', ratio: 10, agentCount: 5, endTime: '2029-09-30', status: true },
{ uid: '38837', name: 'yuyuyuyu', code: '96970376', ratio: 50, agentCount: 0, endTime: '2024-09-05', status: true },
{ uid: '46444', name: '瓒呯骇浜嬩笟閮?, code: '41026828', ratio: 30, agentCount: 1, endTime: '2026-09-03', status: true }
])
function onSearch() {
uni.showToast({ title: '鏌ヨ涓?..', icon: 'none' })
}
function onAdd() {
uni.showToast({ title: '娣诲姞浜嬩笟閮?, icon: 'none' })
}
</script>
<style scoped lang="scss">
/* Use similar styles as others */
.admin-page { /* padding removed */ }
.filter-card { background: #fff; padding: 24px; margin-bottom: 16px; border-radius: 4px; }
.filter-row { display: flex; flex-direction: row; align-items: center; gap: 24px; }
.label { font-size: 14px; color: #333; }
.filter-input { border: 1px solid #d9d9d9; height: 32px; width: 220px; padding: 0 12px; font-size: 14px; }
.btn { height: 32px; padding: 0 16px; font-size: 14px; border: 1px solid #d9d9d9; background: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.btn.primary { background: #1890ff; border-color: #1890ff; color: #fff; }
.btn.small { height: 32px; }
.content-card { background: #fff; border-radius: 4px; }
.action-bar { padding: 16px 24px; }
.table-container { padding: 0 24px 24px; }
.table-header { display: flex; flex-direction: row; background: #f8faff; border-bottom: 1px solid #f0f0f0; padding: 12px 0; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; padding: 12px 0; align-items: center; &:hover { background: #fafafa; } }
.col { padding: 0 8px; font-size: 14px; color: #333; display: flex; align-items: center; }
.col-uid { width: 80px; }
.col-avatar { width: 80px; justify-content: center; }
.col-name { width: 120px; }
.col-code { width: 100px; justify-content: center; }
.col-ratio { width: 100px; justify-content: center; }
.col-count { width: 100px; justify-content: center; }
.col-time { width: 120px; justify-content: center; }
.col-status { width: 80px; justify-content: center; }
.col-ops { flex: 1; justify-content: flex-end; }
.avatar-img { width: 32px; height: 32px; border-radius: 2px; }
.op-link { color: #1890ff; cursor: pointer; }
.op-divider { color: #e8e8e8; margin: 0 8px; }
</style>

View File

@@ -0,0 +1,258 @@
<template>
<view class="admin-page">
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">鏄惁鏄剧ず锛?/text>
<view class="select-mock">
<text>鍏ㄩ儴</text>
<text class="arrow">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="label">绛夌骇鍚嶇О锛?/text>
<input class="filter-input" placeholder="璇疯緭鍏ョ瓑绾у悕绉? />
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">鏌ヨ</button>
</view>
</view>
</view>
<view class="content-card">
<view class="action-bar">
<button class="btn primary small" @click="onAdd">娣诲姞绛夌骇</button>
</view>
<view class="table-container">
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
<view class="col col-img"><text>鍟嗗搧鍥剧墖</text></view>
<view class="col col-name"><text>鍚嶇О</text></view>
<view class="col col-level"><text>绛夌骇</text></view>
<view class="col col-percent"><text>涓€绾у垎浣f瘮渚?/text></view>
<view class="col col-percent"><text>浜岀骇鍒嗕剑姣斾緥</text></view>
<view class="col col-stat"><text>浠诲姟鎬绘暟</text></view>
<view class="col col-stat"><text>闇€瀹屾垚鏁伴噺</text></view>
<view class="col col-status"><text>鏄惁鏄剧ず</text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<view class="table-body">
<view v-for="item in levelList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-img">
<image class="table-img" src="/static/logo.png" mode="aspectFill" />
</view>
<view class="col col-name"><text>{{ item.name }}</text></view>
<view class="col col-level"><text>{{ item.level }}</text></view>
<view class="col col-percent"><text>{{ item.percent1 }}%</text></view>
<view class="col col-percent"><text>{{ item.percent2 }}%</text></view>
<view class="col col-stat"><text>{{ item.taskTotal }}</text></view>
<view class="col col-stat"><text>{{ item.taskFinish }}</text></view>
<view class="col col-status">
<switch :checked="item.show" color="#1890ff" scale="0.8" />
</view>
<view class="col col-ops">
<text class="op-link">绛夌骇浠诲姟</text>
<text class="op-divider">|</text>
<text class="op-link">缂栬緫</text>
<text class="op-divider">|</text>
<text class="op-link">鍒犻櫎</text>
</view>
</view>
</view>
</view>
<view class="pagination">
<text class="page-info">鍏?{{ levelList.length }} 鏉?/text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const levelList = ref([
{ id: '1', name: '涓€绾у垎閿€鍛?, level: 1, percent1: 0.00, percent2: 0.00, taskTotal: 0, taskFinish: 0, show: true },
{ id: '2', name: '浜岀骇鍒嗛攢鍛?, level: 2, percent1: 0.00, percent2: 0.00, taskTotal: 0, taskFinish: 0, show: true },
{ id: '3', name: '涓夌骇鍒嗛攢鍛?, level: 3, percent1: 0.00, percent2: 0.00, taskTotal: 0, taskFinish: 0, show: true },
{ id: '4', name: '鍥涚骇鍒嗛攢鍛?, level: 4, percent1: 0.00, percent2: 0.00, taskTotal: 0, taskFinish: 0, show: true },
{ id: '5', name: '浜旂骇鍒嗛攢鍛?, level: 5, percent1: 0.00, percent2: 0.00, taskTotal: 0, taskFinish: 0, show: true }
])
function onSearch() {
uni.showToast({ title: '鏌ヨ涓?..', icon: 'none' })
}
function onAdd() {
uni.showToast({ title: '寮€濮嬫坊鍔?, icon: 'none' })
}
</script>
<style scoped lang="scss">
.admin-page {
/* padding removed */
}
.filter-card {
background: #fff;
border-radius: 4px;
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #333;
}
.select-mock {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border: 1px solid #d9d9d9;
border-radius: 2px;
height: 32px;
width: 160px;
padding: 0 12px;
background: #fff;
text { font-size: 14px; color: #666; }
.arrow { font-size: 10px; color: #bfbfbf; }
}
.filter-input {
border: 1px solid #d9d9d9;
border-radius: 2px;
height: 32px;
width: 220px;
padding: 0 12px;
font-size: 14px;
}
.btn {
height: 32px;
padding: 0 16px;
font-size: 14px;
border-radius: 2px;
border: 1px solid #d9d9d9;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin: 0;
}
.btn.primary {
background: #1890ff;
border-color: #1890ff;
color: #fff;
}
.btn.small {
height: 32px;
padding: 0 12px;
}
.content-card {
background: #fff;
border-radius: 4px;
padding: 0;
}
.action-bar {
padding: 16px 24px;
}
.table-container {
padding: 0 24px 24px;
}
.table-header {
display: flex;
flex-direction: row;
background: #f8faff;
border-bottom: 1px solid #f0f0f0;
padding: 12px 0;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
padding: 12px 0;
align-items: center;
&:hover {
background: #fafafa;
}
}
.col {
padding: 0 8px;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.col-id { width: 50px; }
.col-img { width: 80px; justify-content: center; }
.col-name { width: 120px; }
.col-level { width: 80px; justify-content: center; }
.col-percent { width: 120px; justify-content: center; }
.col-stat { width: 100px; justify-content: center; }
.col-status { width: 100px; justify-content: center; }
.col-ops {
flex: 1;
justify-content: flex-end;
padding-right: 16px;
}
.table-img {
width: 32px;
height: 32px;
border-radius: 2px;
}
.op-link {
color: #1890ff;
cursor: pointer;
font-size: 14px;
}
.op-divider {
color: #e8e8e8;
margin: 0 8px;
}
.pagination {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
}
.page-info {
font-size: 14px;
color: #999;
}
</style>

View File

@@ -0,0 +1,305 @@
<template>
<view class="admin-page">
<!-- 绛涢€夐潰鏉?-->
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">鏃堕棿閫夋嫨锛?/text>
<view class="date-picker-mock">
<text class="placeholder">寮€濮嬫棩鏈?- 缁撴潫鏃ユ湡</text>
<text class="icon-calendar">馃搮</text>
</view>
</view>
<view class="filter-item">
<text class="label">鎼滅储锛?/text>
<input class="filter-input" placeholder="璇疯緭鍏ュ鍚嶃€佺數璇濄€乁ID" />
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">鏌ヨ</button>
</view>
</view>
</view>
<!-- 鍐呭鍗$墖 -->
<view class="content-card">
<view class="action-bar">
<button class="btn ghost small" @click="onExport">瀵煎嚭</button>
</view>
<view class="table-container">
<!-- 琛ㄥご -->
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
<view class="col col-img"><text>鍟嗗搧鍥剧墖</text></view>
<view class="col col-info"><text>鐢ㄦ埛淇℃伅</text></view>
<view class="col col-level"><text>鍒嗛攢绛夌骇</text></view>
<view class="col col-stat"><text>鎺ㄥ箍鐢ㄦ埛鏁伴噺</text></view>
<view class="col col-stat"><text>鎺ㄥ箍璁㈠崟鏁伴噺</text></view>
<view class="col col-stat"><text>鎺ㄥ箍璁㈠崟閲戦</text></view>
<view class="col col-stat"><text>浣i噾鎬婚噾棰?/text></view>
<view class="col col-stat"><text>宸叉彁鐜伴噾棰?/text></view>
<view class="col col-stat"><text>鎻愮幇娆℃暟</text></view>
<view class="col col-stat"><text>鏈彁鐜伴噾棰?/text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<!-- 琛ㄦ牸鍐呭 -->
<view class="table-body">
<view v-for="item in promoterList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-img">
<image class="table-img" src="/static/logo.png" mode="aspectFill" />
</view>
<view class="col col-info">
<view class="user-info-box">
<text class="info-text">鏄电О:{{ item.nickname }}</text>
<text class="info-text">濮撳悕:{{ item.name }}</text>
<text class="info-text">鐢佃瘽:{{ item.phone }}</text>
</view>
</view>
<view class="col col-level"><text>{{ item.level }}</text></view>
<view class="col col-stat"><text>{{ item.userCount }}</text></view>
<view class="col col-stat"><text>{{ item.orderCount }}</text></view>
<view class="col col-stat"><text>{{ item.orderAmount }}</text></view>
<view class="col col-stat"><text>{{ item.commissionTotal }}</text></view>
<view class="col col-stat"><text>{{ item.withdrawnAmount }}</text></view>
<view class="col col-stat"><text>{{ item.withdrawCount }}</text></view>
<view class="col col-stat"><text>{{ item.unwithdrawnAmount }}</text></view>
<view class="col col-ops">
<text class="op-link" @click="onPromoter(item)">鎺ㄥ箍浜?/text>
<text class="op-divider">|</text>
<text class="op-link" @click="onMore(item)">鏇村</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
</view>
</view>
<!-- 鍒嗛〉 (妯℃嫙) -->
<view class="pagination">
<text class="page-info">鍏?{{ promoterList.length }} 鏉?/text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const promoterList = ref([
{ id: '82764', nickname: '183****5762', name: '-', phone: '183****5762', level: '--', userCount: 0, orderCount: 0, orderAmount: '0.00', commissionTotal: '0.00', withdrawnAmount: 0, withdrawCount: 0, unwithdrawnAmount: 0 },
{ id: '82763', nickname: '1', name: '-', phone: '185****4518', level: '--', userCount: 0, orderCount: 0, orderAmount: '0.00', commissionTotal: '0.00', withdrawnAmount: 0, withdrawCount: 0, unwithdrawnAmount: 0 },
{ id: '82762', nickname: 'Ronnie', name: '-', phone: '153****0391', level: '--', userCount: 0, orderCount: 0, orderAmount: '0.00', commissionTotal: '0.00', withdrawnAmount: 0, withdrawCount: 0, unwithdrawnAmount: 0 },
{ id: '82761', nickname: 'Charon', name: '-', phone: '181****6248', level: '--', userCount: 0, orderCount: 0, orderAmount: '0.00', commissionTotal: '0.00', withdrawnAmount: 0, withdrawCount: 0, unwithdrawnAmount: 0 },
{ id: '82760', nickname: '11', name: '-', phone: '177****0265', level: '--', userCount: 0, orderCount: 0, orderAmount: '0.00', commissionTotal: '0.00', withdrawnAmount: 0, withdrawCount: 0, unwithdrawnAmount: 0 }
])
function onSearch() {
uni.showToast({ title: '鏌ヨ涓?..', icon: 'none' })
}
function onExport() {
uni.showToast({ title: '寮€濮嬪鍑?, icon: 'none' })
}
function onPromoter(item: any) {
uni.showToast({ title: '鏌ョ湅鎺ㄥ箍浜? ' + item.id, icon: 'none' })
}
function onMore(item: any) {
uni.showToast({ title: '鏇村鎿嶄綔: ' + item.id, icon: 'none' })
}
</script>
<style scoped lang="scss">
.admin-page {
/* padding removed */
}
.filter-card {
background: #fff;
border-radius: 4px;
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #333;
}
.date-picker-mock {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border: 1px solid #d9d9d9;
border-radius: 2px;
height: 32px;
width: 260px;
padding: 0 12px;
background: #fff;
.placeholder { font-size: 14px; color: #bfbfbf; }
.icon-calendar { font-size: 14px; color: #bfbfbf; }
}
.filter-input {
border: 1px solid #d9d9d9;
border-radius: 2px;
height: 32px;
width: 220px;
padding: 0 12px;
font-size: 14px;
}
.filter-btns {
display: flex;
}
.btn {
height: 32px;
padding: 0 16px;
font-size: 14px;
border-radius: 2px;
border: 1px solid #d9d9d9;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin: 0;
}
.btn.primary {
background: #2f54eb;
border-color: #2f54eb;
color: #fff;
}
.btn.ghost {
color: #666;
background: #fff;
}
.btn.small {
height: 28px;
padding: 0 12px;
font-size: 13px;
}
.content-card {
background: #fff;
border-radius: 4px;
padding: 0;
}
.action-bar {
padding: 16px 24px;
}
.table-container {
padding: 0 24px 24px;
}
.table-header {
display: flex;
flex-direction: row;
background: #f8faff;
border-bottom: 1px solid #f0f0f0;
padding: 12px 0;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
padding: 12px 0;
align-items: center;
&:hover {
background: #fafafa;
}
}
.col {
padding: 0 8px;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.col-id { width: 60px; }
.col-img { width: 80px; justify-content: center; }
.col-info { width: 180px; }
.col-level { width: 100px; justify-content: center; }
.col-stat { width: 110px; justify-content: center; }
.col-ops {
flex: 1;
justify-content: flex-end;
padding-right: 16px;
}
.table-img {
width: 40px;
height: 40px;
border-radius: 4px;
}
.user-info-box {
display: flex;
flex-direction: column;
}
.info-text {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.op-link {
color: #1890ff;
cursor: pointer;
font-size: 14px;
}
.op-divider {
color: #e8e8e8;
margin: 0 8px;
}
.arrow-down {
font-size: 10px;
color: #1890ff;
margin-left: 4px;
}
.pagination {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
}
.page-info {
font-size: 14px;
color: #999;
}
</style>

View File

@@ -0,0 +1,508 @@
<template>
<view class="admin-page">
<view class="content-card">
<view class="tabs-row">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === index }"
@click="activeTab = index"
>
<text>{{ tab }}</text>
</view>
</view>
<view class="form-container">
<!-- 分销模式 -->
<view v-if="activeTab === 0" class="form-content">
<view class="form-item">
<text class="label">分销启用:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.statue = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.statue === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.statue === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">商城分销功能开启关闭</text>
</view>
<view class="form-item">
<text class="label">分销模式:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.extract_type = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.extract_type === '1'" color="#1890ff" /> 指定分销
</label>
<label class="radio-label">
<radio value="2" :checked="form.extract_type === '2'" color="#1890ff" /> 人人分销
</label>
<label class="radio-label">
<radio value="3" :checked="form.extract_type === '3'" color="#1890ff" /> 满额分销
</label>
</radio-group>
<text class="hint">人人分销"默认每个人都可以分销,“指定分销”仅后台手动设置推广员,“满额分销”指用户购买商品满足消费金额后自动开启分销</text>
</view>
<view class="form-item">
<text class="label">分销关系绑定:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.bind_type = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.bind_type === '1'" color="#1890ff" /> 所有用户
</label>
<label class="radio-label">
<radio value="2" :checked="form.bind_type === '2'" color="#1890ff" /> 新用户
</label>
</radio-group>
<text class="hint">所有用户”指所有没有上级推广人的用户点击或扫码即绑定分销关系,“新用户”指新注册的用户或首次进入系统的用户才会绑定推广关系</text>
</view>
<view class="form-item">
<text class="label">绑定模式:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.store_brokerage_binding_status = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.store_brokerage_binding_status === '1'" color="#1890ff" /> 永久
</label>
<label class="radio-label">
<radio value="2" :checked="form.store_brokerage_binding_status === '2'" color="#1890ff" /> 有效期
</label>
<label class="radio-label">
<radio value="3" :checked="form.store_brokerage_binding_status === '3'" color="#1890ff" /> 临时
</label>
</radio-group>
<text class="hint">永久”一次绑定永久有效,“有效期”绑定后一段时间内有效,“临时”临时有效</text>
</view>
<view class="form-item">
<text class="label">分销海报图:</text>
<view class="poster-upload">
<image class="poster-preview" v-if="form.brokerage_poster_status" :src="form.brokerage_poster_status" mode="aspectFill"></image>
<view v-else class="upload-btn" @click="onUploadPoster">
<text class="plus">+</text>
</view>
</view>
<text class="hint">个人中心分销海报图片建议尺寸600x1000</text>
</view>
<view class="form-item">
<text class="label">分销层级:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.brokerage_level = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.brokerage_level === '1'" color="#1890ff" /> 一级分销
</label>
<label class="radio-label">
<radio value="2" :checked="form.brokerage_level === '2'" color="#1890ff" /> 二级分销
</label>
</radio-group>
<text class="hint">分销层级,一级是只返上一层的佣金,二级是返上级 and 上上级的佣金</text>
</view>
<view class="form-item">
<text class="label">事业部开关:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.is_area_manager = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.is_area_manager === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.is_area_manager === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">事业部开关,关闭后不计算事业部佣金</text>
</view>
<view class="form-item">
<text class="label">代理商申请开关:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.is_agent_apply = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.is_agent_apply === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.is_agent_apply === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">控制移动端我的推广页面的代理商申请按钮是否显示</text>
</view>
<view class="form-item">
<text class="label">佣金悬浮窗开关:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.is_commission_window = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.is_commission_window === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.is_commission_window === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">佣金悬浮窗开关,关闭之后,商品详情不显示佣金悬浮窗</text>
</view>
<button class="submit-btn" @click="onSubmit">提交</button>
</view>
<!-- 返佣设置 -->
<view v-if="activeTab === 1" class="form-content">
<view class="form-item">
<text class="label">自购返佣:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.is_self_brokerage = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.is_self_brokerage === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.is_self_brokerage === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">是否开启自购返佣(开启:分销员自己购买商品,享受一级返佣,上级享受二级返佣;关闭:分销员自己购买商品没有返佣)</text>
</view>
<view class="form-item">
<text class="label">购买付费会员返佣:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.is_member_brokerage = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.is_member_brokerage === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.is_member_brokerage === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">购买付费会员是否按照设置的佣金比例进行返佣</text>
</view>
<view class="form-item">
<text class="label">返佣类型:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.brokerage_type = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.brokerage_type === '1'" color="#1890ff" /> 按照商品价格返佣
</label>
<label class="radio-label">
<radio value="2" :checked="form.brokerage_type === '2'" color="#1890ff" /> 按照实际支付价格返佣
</label>
</radio-group>
<text class="hint">选择返佣类型,按照商品价格返佣(按照商品售价计算返佣金额)以及按照实际支付价格返佣(按照商品的实际支付价格计算返佣)</text>
</view>
<view class="form-item">
<text class="label">推广用户返佣:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.is_promoter_brokerage = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.is_promoter_brokerage === '1'" color="#1890ff" /> 开启
</label>
<label class="radio-label">
<radio value="0" :checked="form.is_promoter_brokerage === '0'" color="#1890ff" /> 关闭
</label>
</radio-group>
<text class="hint">分销推广用户获取佣金</text>
</view>
<view class="form-item">
<text class="label">推广佣金单价:</text>
<view class="input-with-unit">
<input class="form-input" :value="form.promoter_brokerage_price" @input="(e: UniInputEvent) => form.promoter_brokerage_price = e.detail.value" />
</view>
<text class="hint">分销推广佣金单价(每推广一个用户)</text>
</view>
<view class="form-item">
<text class="label">每日推广佣金上限:</text>
<view class="input-with-unit">
<input class="form-input" :value="form.promoter_brokerage_day_max" @input="(e: UniInputEvent) => form.promoter_brokerage_day_max = e.detail.value" />
</view>
<text class="hint">每日推广佣金上限0:不发佣金;-1:不限制;最好是推广佣金单价的整数倍)</text>
</view>
<view class="form-item">
<text class="label">一级返佣比例:</text>
<view class="input-with-unit">
<input class="form-input" :value="form.store_brokerage_ratio" @input="(e: UniInputEvent) => form.store_brokerage_ratio = e.detail.value" />
</view>
<text class="hint">订单交易成功后给上级返佣的比例0 - 100,例:5 = 反订单商品金额的5%</text>
</view>
<view class="form-item">
<text class="label">二级返佣比例:</text>
<view class="input-with-unit">
<input class="form-input" :value="form.store_brokerage_two_ratio" @input="(e: UniInputEvent) => form.store_brokerage_two_ratio = e.detail.value" />
</view>
<text class="hint">订单交易成功后给上上级返佣的比例0 - 100,例:5 = 反订单商品金额 of 5%</text>
</view>
<view class="form-item">
<text class="label">冻结时间:</text>
<view class="input-with-unit">
<input class="form-input" :value="form.extract_frozen_time" @input="(e: UniInputEvent) => form.extract_frozen_time = e.detail.value" />
</view>
<text class="hint">防止用户退款,佣金被提现了,所以需要设置佣金冻结时间(天)</text>
</view>
<button class="submit-btn" @click="onSubmit">提交</button>
</view>
<!-- 提现设置 -->
<view v-if="activeTab === 2" class="form-content">
<view class="form-item">
<text class="label">提现最低金额:</text>
<input class="form-input" :value="form.user_extract_min_price" @input="(e: UniInputEvent) => form.user_extract_min_price = e.detail.value" />
<text class="hint">用户提现最低金额限制</text>
</view>
<view class="form-item">
<text class="label">提现银行卡:</text>
<textarea class="form-textarea" :value="form.extract_bank_list" @input="(e: UniInputEvent) => form.extract_bank_list = e.detail.value" placeholder="配置提现银行卡类型,每个银行换行"></textarea>
<text class="hint">配置提现银行卡类型,每个银行换行</text>
</view>
<view class="form-item">
<text class="label">提现方式:</text>
<checkbox-group class="checkbox-group" @change="(e: UniCheckboxGroupChangeEvent) => form.extract_type_list = e.detail.value">
<label class="checkbox-label"><checkbox value="bank" :checked="form.extract_type_list.includes('bank')" color="#1890ff" /> 银行卡</label>
<label class="checkbox-label"><checkbox value="wechat" :checked="form.extract_type_list.includes('wechat')" color="#1890ff" /> 微信</label>
<label class="checkbox-label"><checkbox value="alipay" :checked="form.extract_type_list.includes('alipay')" color="#1890ff" /> 支付宝</label>
</checkbox-group>
<text class="hint">开启后用户可以选择该提现方式</text>
</view>
<view class="form-item">
<text class="label">微信提现:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.wechat_extract_type = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.wechat_extract_type === '1'" color="#1890ff" /> 手动线下转账
</label>
<label class="radio-label">
<radio value="2" :checked="form.wechat_extract_type === '2'" color="#1890ff" /> 自动转账到零钱
</label>
</radio-group>
<text class="hint">微信提现方式:手动线下转账,自动转账到零钱(需开通商家转账到零钱)</text>
</view>
<view class="form-item">
<text class="label">支付宝提现:</text>
<radio-group class="radio-group" @change="(e: UniRadioGroupChangeEvent) => form.alipay_extract_type = e.detail.value">
<label class="radio-label">
<radio value="1" :checked="form.alipay_extract_type === '1'" color="#1890ff" /> 手动线下转账
</label>
<label class="radio-label">
<radio value="2" :checked="form.alipay_extract_type === '2'" color="#1890ff" /> 自动转账到余额
</label>
</radio-group>
<text class="hint">支付宝提现方式:手动线下转账,自动转账到余额(需开通支付宝转账)</text>
</view>
<view class="form-item">
<text class="label">提现手续费:</text>
<input class="form-input" :value="form.user_extract_fee" @input="(e: UniInputEvent) => form.user_extract_fee = e.detail.value" />
<text class="hint">提现手续费百分比范围0-1000为无提现手续费设置10即收取10%手续费提现100元到账90元10元手续费</text>
</view>
<button class="submit-btn" @click="onSubmit">提交</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const activeTab = ref(0)
const tabs = ['分销模式', '返佣设置', '提现设置']
const form = ref({
// 分销模式
statue: '1',
extract_type: '2',
bind_type: '2',
store_brokerage_binding_status: '1',
brokerage_poster_status: '',
brokerage_level: '2',
is_area_manager: '1',
is_agent_apply: '1',
is_commission_window: '1',
// 返佣设置
is_self_brokerage: '1',
is_member_brokerage: '0',
brokerage_type: '1',
is_promoter_brokerage: '1',
promoter_brokerage_price: '2',
promoter_brokerage_day_max: '-1',
store_brokerage_ratio: '20',
store_brokerage_two_ratio: '2',
extract_frozen_time: '1',
// 提现设置
user_extract_min_price: '1',
extract_bank_list: '中国银行',
extract_type_list: ['bank', 'wechat', 'alipay'],
wechat_extract_type: '1',
alipay_extract_type: '1',
user_extract_fee: '0'
})
function onUploadPoster() {
uni.chooseImage({
count: 1,
success: (res) => {
form.value.brokerage_poster_status = res.tempFilePaths[0]
}
})
}
function onSubmit() {
uni.showToast({ title: '保存成功', icon: 'success' })
}
</script>
<style scoped lang="scss">
.admin-page {
display: flex;
flex-direction: column;
}
.content-card {
background: #fff;
border-radius: 4px;
}
.tabs-row {
display: flex;
flex-direction: row;
padding: 0 24px;
border-bottom: 1px solid #f0f0f0;
}
.tab-item {
padding: 16px 20px;
cursor: pointer;
position: relative;
text { font-size: 15px; color: #666; }
&.active {
text { color: #1890ff; font-weight: 500; }
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #1890ff;
}
}
}
.form-container {
padding: 32px 24px;
}
.form-item {
margin-bottom: 24px;
display: flex;
flex-direction: column;
}
.label {
font-size: 14px;
color: #333;
margin-bottom: 8px;
font-weight: 500;
}
.radio-group, .checkbox-group {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16px;
}
.radio-label, .checkbox-label {
display: flex;
flex-direction: row;
align-items: center;
font-size: 14px;
color: #666;
}
.hint {
font-size: 12px;
color: #999;
margin-top: 8px;
line-height: 1.5;
max-width: 800px;
}
.form-input {
border: 1px solid #d9d9d9;
border-radius: 4px;
height: 36px;
padding: 0 12px;
font-size: 14px;
width: 100%;
max-width: 400px;
}
.form-textarea {
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 8px 12px;
font-size: 14px;
width: 100%;
max-width: 600px;
height: 120px;
}
.poster-upload {
width: 80px;
height: 80px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background-color: #fafafa;
&:hover {
border-color: #1890ff;
}
}
.poster-preview {
width: 100%;
height: 100%;
border-radius: 4px;
}
.upload-btn {
display: flex;
align-items: center;
justify-content: center;
.plus {
font-size: 24px;
color: #999;
}
}
.input-with-unit {
display: flex;
flex-direction: row;
align-items: center;
}
.submit-btn {
margin-top: 24px;
width: 80px;
height: 36px;
background: #1890ff;
color: #fff;
border-radius: 4px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:active {
opacity: 0.8;
}
}
</style>

View File

@@ -1,63 +1,63 @@
<template>
<template>
<view class="finance-balance-stats">
<!-- 顶部数据统计卡片 (3列布局) -->
<!-- 椤堕儴鏁版嵁缁熻鍗$墖 (3鍒楀竷灞€) -->
<view class="stats-grid">
<view class="stat-card border-shadow">
<view class="stat-icon-circle bg-blue">
<text class="icon-white">💰</text>
<text class="icon-white">馃挵</text>
</view>
<view class="stat-content">
<text class="stat-value">1447117274.55</text>
<text class="stat-label">当前余额</text>
<text class="stat-label">褰撳墠浣欓</text>
</view>
</view>
<view class="stat-card border-shadow">
<view class="stat-icon-circle bg-orange">
<text class="icon-white">🏦</text>
<text class="icon-white">馃彟</text>
</view>
<view class="stat-content">
<text class="stat-value">1602611838.49</text>
<text class="stat-label">累计余额</text>
<text class="stat-label">绱浣欓</text>
</view>
</view>
<view class="stat-card border-shadow">
<view class="stat-icon-circle bg-green">
<text class="icon-white">💳</text>
<text class="icon-white">馃挸</text>
</view>
<view class="stat-content">
<text class="stat-value">155494563.94</text>
<text class="stat-label">累计消耗余额</text>
<text class="stat-label">绱娑堣€椾綑棰?/text>
</view>
</view>
</view>
<!-- 时间筛选区 -->
<!-- 鏃堕棿绛涢€夊尯 -->
<view class="filter-bar border-shadow">
<view class="filter-item">
<text class="filter-label">时间选择:</text>
<text class="filter-label">鏃堕棿閫夋嫨:</text>
<view class="date-picker-wrap">
<text class="calendar-icon">📅</text>
<text class="calendar-icon">馃搮</text>
<text class="date-range">2026/01/05 - 2026/02/03</text>
</view>
</view>
</view>
<!-- 趋势图表区 (CRMEB 1:1) -->
<!-- 瓒嬪娍鍥捐〃鍖?(CRMEB 1:1) -->
<view class="chart-box border-shadow">
<view class="chart-header">
<text class="chart-title">余额使用趋势</text>
<text class="chart-title">浣欓浣跨敤瓒嬪娍</text>
<view class="chart-legend">
<view class="legend-item">
<view class="dot blue-dot"></view>
<text class="legend-txt">余额积累</text>
<text class="legend-txt">浣欓绉疮</text>
</view>
<view class="legend-item">
<view class="dot green-dot"></view>
<text class="legend-txt">余额消耗</text>
<text class="legend-txt">浣欓娑堣€?/text>
</view>
</view>
<view class="chart-ops">
<text class="op-icon">📥</text>
<text class="op-icon">馃摜</text>
</view>
</view>
<view class="main-chart-wrap">
@@ -65,26 +65,26 @@
</view>
</view>
<!-- 底部双列分析 -->
<!-- 搴曢儴鍙屽垪鍒嗘瀽 -->
<view class="bottom-analysis">
<view class="analysis-box border-shadow">
<view class="box-header">
<text class="box-title">余额来源分析</text>
<text class="box-title">浣欓鏉ユ簮鍒嗘瀽</text>
<view class="btn-toggle" @click="toggleSourceStyle">
<text class="toggle-txt">切换样式</text>
<text class="toggle-txt">鍒囨崲鏍峰紡</text>
</view>
</view>
<view class="chart-container">
<!-- 样式 0: 图表 -->
<!-- 鏍峰紡 0: 鍥捐〃 -->
<EChartsView v-if="sourceStyleMode == 0 && sourceOption != null" :option="sourceOption" class="pie-chart" />
<!-- 样式 1: 列表 -->
<!-- 鏍峰紡 1: 鍒楄〃 -->
<view v-if="sourceStyleMode == 1" class="stats-table">
<view class="table-header">
<text class="th col-idx">序号</text>
<text class="th col-name">来源</text>
<text class="th col-amount">金额</text>
<text class="th col-percent">占比率</text>
<text class="th col-idx">搴忓彿</text>
<text class="th col-name">鏉ユ簮</text>
<text class="th col-amount">閲戦</text>
<text class="th col-percent">鍗犳瘮鐜?/text>
</view>
<scroll-view class="table-body">
<view class="table-row" v-for="(item, index) in sourceData" :key="index">
@@ -104,22 +104,22 @@
</view>
<view class="analysis-box border-shadow">
<view class="box-header">
<text class="box-title">余额消耗</text>
<text class="box-title">浣欓娑堣€?/text>
<view class="btn-toggle" @click="toggleConsumptionStyle">
<text class="toggle-txt">切换样式</text>
<text class="toggle-txt">鍒囨崲鏍峰紡</text>
</view>
</view>
<view class="chart-container">
<!-- 样式 0: 图表 -->
<!-- 鏍峰紡 0: 鍥捐〃 -->
<EChartsView v-if="consumptionStyleMode == 0 && consumptionOption != null" :option="consumptionOption" class="pie-chart" />
<!-- 样式 1: 列表 -->
<!-- 鏍峰紡 1: 鍒楄〃 -->
<view v-if="consumptionStyleMode == 1" class="stats-table">
<view class="table-header">
<text class="th col-idx">序号</text>
<text class="th col-name">来源</text>
<text class="th col-amount">金额</text>
<text class="th col-percent">占比率</text>
<text class="th col-idx">搴忓彿</text>
<text class="th col-name">鏉ユ簮</text>
<text class="th col-amount">閲戦</text>
<text class="th col-percent">鍗犳瘮鐜?/text>
</view>
<scroll-view class="table-body">
<view class="table-row" v-for="(item, index) in consumptionDataList" :key="index">
@@ -149,28 +149,28 @@ const trendOption = ref<any>(null)
const sourceOption = ref<any>(null)
const consumptionOption = ref<any>(null)
// 样式切换状态: 0=图表, 1=列表
// 鏍峰紡鍒囨崲鐘舵€? 0=鍥捐〃, 1=鍒楄〃
const sourceStyleMode = ref(0)
const consumptionStyleMode = ref(0)
// 统计数据 (使用 ref 保证响应式)
// 缁熻鏁版嵁 (浣跨敤 ref 淇濊瘉鍝嶅簲寮?
const sourceData = ref([
{ value: 125000.00, name: '系统增加', percent: 40.00 },
{ value: 93750.00, name: '用户充值', percent: 30.00 },
{ value: 78125.00, name: '佣金提现', percent: 25.00 },
{ value: 62500.00, name: '抽奖赠送', percent: 20.00 },
{ value: 46875.00, name: '商品退款', percent: 15.00 }
{ value: 125000.00, name: '绯荤粺澧炲姞', percent: 40.00 },
{ value: 93750.00, name: '鐢ㄦ埛鍏呭€?, percent: 30.00 },
{ value: 78125.00, name: '浣i噾鎻愮幇', percent: 25.00 },
{ value: 62500.00, name: '鎶藉璧犻€?, percent: 20.00 },
{ value: 46875.00, name: '鍟嗗搧閫€娆?, percent: 15.00 }
])
const consumptionDataList = ref([
{ value: 435692.51, name: '购买商品', percent: 50.00 },
{ value: 8060.18, name: '购买会员', percent: 20.00 },
{ value: 0.00, name: '充值退款', percent: 15.00 },
{ value: 0.00, name: '系统减少', percent: 15.00 }
{ value: 435692.51, name: '璐拱鍟嗗搧', percent: 50.00 },
{ value: 8060.18, name: '璐拱浼氬憳', percent: 20.00 },
{ value: 0.00, name: '鍏呭€奸€€娆?, percent: 15.00 },
{ value: 0.00, name: '绯荤粺鍑忓皯', percent: 15.00 }
])
/**
* 转换 Plain Object 工具
* 杞崲 Plain Object 宸ュ叿
*/
function toPlainObject(obj : any) : any {
if (obj == null) return null
@@ -225,14 +225,14 @@ function initTrendChart() {
},
series: [
{
name: '余额积累',
name: '浣欓绉疮',
type: 'line',
smooth: true,
data: accumulationData,
itemStyle: { color: '#1890ff' }
},
{
name: '余额消耗',
name: '浣欓娑堣€?,
type: 'line',
smooth: true,
data: consumptionData,
@@ -250,12 +250,12 @@ function initSourceChart() {
color: ['#5b8ff9', '#5ad8a6', '#5d7092', '#f6bd16', '#e8684a'],
series: [
{
name: '余额来源',
name: '浣欓鏉ユ簮',
type: 'pie',
radius: '70%',
center: ['40%', '50%'],
label: { show: true, fontSize: 11, formatter: '{b}\n{c}%' },
// 关键点:将图表数据映射到 percent 字段
// 鍏抽敭鐐癸細灏嗗浘琛ㄦ暟鎹槧灏勫埌 percent 瀛楁
data: sourceData.value.map(item => ({ value: item.percent, name: item.name }))
}
]
@@ -270,12 +270,12 @@ function initConsumptionChart() {
color: ['#5b8ff9', '#5ad8a6', '#5d7092', '#f6bd16'],
series: [
{
name: '余额消耗',
name: '浣欓娑堣€?,
type: 'pie',
radius: '70%',
center: ['40%', '50%'],
label: { show: true, fontSize: 11, formatter: '{b}\n{c}%' },
// 关键点:将图表数据映射到 percent 字段
// 鍏抽敭鐐癸細灏嗗浘琛ㄦ暟鎹槧灏勫埌 percent 瀛楁
data: consumptionDataList.value.map(item => ({ value: item.percent, name: item.name }))
}
]
@@ -311,7 +311,7 @@ function toggleConsumptionStyle() {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
/* 顶部卡片 */
/* 椤堕儴鍗$墖 */
.stats-grid {
display: flex;
flex-direction: row;
@@ -365,7 +365,7 @@ function toggleConsumptionStyle() {
margin-top: 4px;
}
/* 时间筛选区 */
/* 鏃堕棿绛涢€夊尯 */
.filter-bar {
padding: 16px 24px;
margin-bottom: 20px;
@@ -397,7 +397,7 @@ function toggleConsumptionStyle() {
.calendar-icon { font-size: 14px; color: #c0c4cc; margin-right: 10px; }
.date-range { font-size: 14px; color: #606266; }
/* 趋势图表区 */
/* 瓒嬪娍鍥捐〃鍖?*/
.chart-box {
padding: 24px;
margin-bottom: 20px;
@@ -436,7 +436,7 @@ function toggleConsumptionStyle() {
height: 100%;
}
/* 底部分析 */
/* 搴曢儴鍒嗘瀽 */
.bottom-analysis {
display: flex;
flex-direction: row;
@@ -478,7 +478,7 @@ function toggleConsumptionStyle() {
height: 100%;
}
/* 列表样式 */
/* 鍒楄〃鏍峰紡 */
.stats-table {
display: flex;
flex-direction: column;
@@ -521,7 +521,7 @@ function toggleConsumptionStyle() {
.progress-container {
flex: 1;
height: 8px;
background-color: #f5f5f5;
border-radius: 4px;
margin-right: 10px;
overflow: hidden;
@@ -539,3 +539,4 @@ function toggleConsumptionStyle() {
color: #666;
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">财务记录</text>
<text class="page-title">璐㈠姟璁板綍</text>
<text class="page-subtitle">Component: FinanceRecord</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 财务记录 的具体功能
// TODO: 瀹炵幇 璐㈠姟璁板綍 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,14 +1,14 @@
<template>
<template>
<view class="finance-withdrawal">
<!-- 头部筛选区 -->
<!-- 澶撮儴绛涢€夊尯 -->
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="filter-label">时间选择:</text>
<input class="date-input" type="text" v-model="timeRangeText" placeholder="请选择时间范围" />
<text class="filter-label">鏃堕棿閫夋嫨:</text>
<input class="date-input" type="text" v-model="timeRangeText" placeholder="璇烽€夋嫨鏃堕棿鑼冨洿" />
</view>
<view class="filter-item">
<text class="filter-label">提现状态:</text>
<text class="filter-label">鎻愮幇鐘舵€?</text>
<picker mode="selector" :range="statusOptions" range-key="text" @change="statusChange">
<view class="picker-view">
<text>{{ statusLabel }}</text>
@@ -17,7 +17,7 @@
</picker>
</view>
<view class="filter-item">
<text class="filter-label">提现方式:</text>
<text class="filter-label">鎻愮幇鏂瑰紡:</text>
<picker mode="selector" :range="methodOptions" range-key="text" @change="methodChange">
<view class="picker-view">
<text>{{ methodLabel }}</text>
@@ -26,14 +26,14 @@
</picker>
</view>
<view class="filter-item search-wrap">
<text class="filter-label">搜索:</text>
<input class="search-input" v-model="searchKeyword" placeholder="微信昵称/姓名/支付宝账号/银行卡号" />
<text class="filter-label">鎼滅储:</text>
<input class="search-input" v-model="searchKeyword" placeholder="寰俊鏄电О/濮撳悕/鏀粯瀹濊处鍙?閾惰鍗″彿" />
</view>
<button class="btn-query" @click="handleQuery">查询</button>
<button class="btn-query" @click="handleQuery">鏌ヨ</button>
</view>
</view>
<!-- 统计指标网格 -->
<!-- 缁熻鎸囨爣缃戞牸 -->
<view class="stats-grid">
<view class="stat-card" v-for="(item, index) in stats" :key="index">
<view class="icon-circle" :class="item.colorClass">
@@ -46,27 +46,27 @@
</view>
</view>
<!-- 操作按钮区 -->
<!-- 鎿嶄綔鎸夐挳鍖?-->
<view class="action-section">
<view class="btn-record">
<text class="btn-record-txt">佣金记录</text>
<text class="btn-record-txt">浣i噾璁板綍</text>
</view>
</view>
<!-- 数据表格 (使用 Flex 模拟以确保兼容性和精度) -->
<!-- 鏁版嵁琛ㄦ牸 (浣跨敤 Flex 妯℃嫙浠ョ‘淇濆吋瀹规€у拰绮惧害) -->
<view class="table-container">
<view class="table-header">
<view class="th col-id"><text class="th-txt">ID</text></view>
<view class="th col-user"><text class="th-txt">用户信息</text></view>
<view class="th col-amount"><text class="th-txt">提现金额</text></view>
<view class="th col-fee"><text class="th-txt">提现手续费</text></view>
<view class="th col-net"><text class="th-txt">到账金额</text></view>
<view class="th col-method"><text class="th-txt">提现方式</text></view>
<view class="th col-qr"><text class="th-txt">收款码</text></view>
<view class="th col-time"><text class="th-txt">申请时间</text></view>
<view class="th col-remark"><text class="th-txt">备注</text></view>
<view class="th col-status"><text class="th-txt">审核状态</text></view>
<view class="th col-ops"><text class="th-txt">操作</text></view>
<view class="th col-user"><text class="th-txt">鐢ㄦ埛淇℃伅</text></view>
<view class="th col-amount"><text class="th-txt">鎻愮幇閲戦</text></view>
<view class="th col-fee"><text class="th-txt">鎻愮幇鎵嬬画璐?/text></view>
<view class="th col-net"><text class="th-txt">鍒拌处閲戦</text></view>
<view class="th col-method"><text class="th-txt">鎻愮幇鏂瑰紡</text></view>
<view class="th col-qr"><text class="th-txt">鏀舵鐮?/text></view>
<view class="th col-time"><text class="th-txt">鐢宠鏃堕棿</text></view>
<view class="th col-remark"><text class="th-txt">澶囨敞</text></view>
<view class="th col-status"><text class="th-txt">瀹℃牳鐘舵€?/text></view>
<view class="th col-ops"><text class="th-txt">鎿嶄綔</text></view>
</view>
<view class="table-body">
@@ -79,7 +79,7 @@
</view>
<view class="user-detail">
<text class="user-nickname">{{ item.nickname }}</text>
<text class="user-id">用户id:{{ item.userId }}</text>
<text class="user-id">鐢ㄦ埛id:{{ item.userId }}</text>
</view>
</view>
</view>
@@ -88,26 +88,26 @@
<view class="td col-net"><text class="td-txt green-txt">{{ item.netAmount }}</text></view>
<view class="td col-method">
<view class="method-box">
<text class="m-line">姓名:{{ item.name }}</text>
<text class="m-line">濮撳悕:{{ item.name }}</text>
<text class="m-line">{{ item.type }}:{{ item.account }}</text>
<text v-if="item.bank" class="m-line">银行开户地址:{{ item.bank }}</text>
<text v-if="item.bank" class="m-line">閾惰寮€鎴峰湴鍧€:{{ item.bank }}</text>
</view>
</view>
<view class="td col-qr">
<view class="qr-box" v-if="item.id === 57">
<text class="qr-icon-txt">■</text>
<text class="qr-icon-txt">鈻?/text>
</view>
</view>
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
<view class="td col-remark"><text class="td-txt">{{ item.remark || '' }}</text></view>
<view class="td col-status"><text class="td-txt">申请中</text></view>
<view class="td col-status"><text class="td-txt">鐢宠涓?/text></view>
<view class="td col-ops">
<view class="ops-box">
<text class="op-btn blue">编辑</text>
<text class="op-btn blue">缂栬緫</text>
<text class="op-sep">|</text>
<text class="op-btn blue">通过</text>
<text class="op-btn blue">閫氳繃</text>
<text class="op-sep">|</text>
<text class="op-btn blue">驳回</text>
<text class="op-btn blue">椹冲洖</text>
</view>
</view>
</view>
@@ -126,61 +126,61 @@ const methodValue = ref('all')
const searchKeyword = ref('')
const statusOptions = ref([
{ value: 'all', text: '全部' },
{ value: '0', text: '待审核' },
{ value: '1', text: '已通过' },
{ value: '-1', text: '已驳回' }
{ value: 'all', text: '鍏ㄩ儴' },
{ value: '0', text: '寰呭鏍? },
{ value: '1', text: '宸查€氳繃' },
{ value: '-1', text: '宸查┏鍥? }
])
const methodOptions = ref([
{ value: 'all', text: '全部' },
{ value: 'alipay', text: '支付宝' },
{ value: 'bank', text: '银行卡' },
{ value: 'weixin', text: '微信' }
{ value: 'all', text: '鍏ㄩ儴' },
{ value: 'alipay', text: '鏀粯瀹? },
{ value: 'bank', text: '閾惰鍗? },
{ value: 'weixin', text: '寰俊' }
])
const statusLabel = computed(() => {
const item = statusOptions.value.find((opt: any) => opt.value === statusValue.value)
return item ? item.text : '全部'
return item ? item.text : '鍏ㄩ儴'
})
const methodLabel = computed(() => {
const item = methodOptions.value.find((opt: any) => opt.value === methodValue.value)
return item ? item.text : '全部'
return item ? item.text : '鍏ㄩ儴'
})
const stats = ref([
{ label: '佣金总金额', value: '676809.25', icon: '$', colorClass: 'blue' },
{ label: '待提现金额', value: '71', icon: '¥', colorClass: 'orange' },
{ label: '已提现金额', value: '68879.25', icon: '$', colorClass: 'green' },
{ label: '未提现金额', value: '607930.00', icon: '¥', colorClass: 'pink' }
{ label: '浣i噾鎬婚噾棰?, value: '676809.25', icon: '$', colorClass: 'blue' },
{ label: '寰呮彁鐜伴噾棰?, value: '71', icon: '', colorClass: 'orange' },
{ label: '宸叉彁鐜伴噾棰?, value: '68879.25', icon: '$', colorClass: 'green' },
{ label: '鏈彁鐜伴噾棰?, value: '607930.00', icon: '', colorClass: 'pink' }
])
const tableData = ref([
{
id: 57,
nickname: '用户昵称: 177****766',
nickname: '鐢ㄦ埛鏄电О: 177****766',
userId: '58837',
amount: '20.00',
fee: '0.00',
netAmount: '20.00',
name: '接口',
type: '支付宝',
name: '鎺ュ彛',
type: '鏀粯瀹?,
account: '1195953899',
time: '2025-10-24 16:04',
remark: ''
},
{
id: 56,
nickname: '用户昵称: 测试员的',
nickname: '鐢ㄦ埛鏄电О: 娴嬭瘯鍛樼殑',
userId: '20695',
amount: '1.00',
fee: '0.00',
netAmount: '1.00',
name: '123',
type: '银行卡',
type: '閾惰鍗?,
account: '4001231221',
bank: '中国银行',
bank: '涓浗閾惰',
time: '2025-07-04 15:11',
remark: ''
}
@@ -206,7 +206,7 @@ const handleQuery = () => {
min-height: 100vh;
}
/* 筛选样式更新 */
/* 绛涢€夋牱寮忔洿鏂?*/
.filter-card {
background-color: #fff;
padding: 24px;
@@ -284,7 +284,7 @@ const handleQuery = () => {
margin-bottom: 16px;
}
/* 统计卡片样式 */
/* 缁熻鍗$墖鏍峰紡 */
.stats-grid {
display: flex;
flex-direction: row;
@@ -338,7 +338,7 @@ const handleQuery = () => {
margin-top: 6px;
}
/* 操作区域 */
/* 鎿嶄綔鍖哄煙 */
.action-section {
margin-bottom: 12px;
display: flex;
@@ -359,9 +359,9 @@ const handleQuery = () => {
font-size: 13px;
}
/* 表格容器样式 (Flex 模拟) */
/* 琛ㄦ牸瀹瑰櫒鏍峰紡 (Flex 妯℃嫙) */
.table-container {
background-color: #fff;
border-radius: 4px;
overflow: hidden;
display: flex;
@@ -411,7 +411,7 @@ const handleQuery = () => {
color: #606266;
}
/* 列宽定义 (与截图匹配) */
/* 鍒楀瀹氫箟 (涓庢埅鍥惧尮閰? */
.col-id { width: 60px; }
.col-user { width: 180px; justify-content: flex-start; }
.col-amount { width: 100px; }
@@ -424,7 +424,7 @@ const handleQuery = () => {
.col-status { width: 100px; }
.col-ops { width: 160px; }
/* 用户信息单元格 */
/* 鐢ㄦ埛淇℃伅鍗曞厓鏍?*/
.user-info-box {
display: flex;
flex-direction: row;
@@ -463,7 +463,7 @@ const handleQuery = () => {
color: #909399;
}
/* 提现方式单元格 */
/* 鎻愮幇鏂瑰紡鍗曞厓鏍?*/
.method-box {
display: flex;
flex-direction: column;
@@ -480,7 +480,7 @@ const handleQuery = () => {
font-weight: 600;
}
/* 收款码 */
/* 鏀舵鐮?*/
.qr-box {
width: 40px;
height: 40px;
@@ -496,7 +496,7 @@ const handleQuery = () => {
font-size: 16px;
}
/* 操作项 */
/* 鎿嶄綔椤?*/
.ops-box {
display: flex;
flex-direction: row;
@@ -515,3 +515,4 @@ const handleQuery = () => {
font-size: 12px;
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-kefu-words">
<view class="words-container">
<!-- 左侧分类 -->
<!-- 宸︿晶鍒嗙被 -->
<view class="category-sidebar border-shadow">
<view class="sidebar-header" @click="handleAddCategory">
<text class="plus-icon">+</text>
<text class="header-txt">添加分类</text>
<text class="header-txt">娣诲姞鍒嗙被</text>
</view>
<scroll-view class="category-list" scroll-y="true">
<view
@@ -14,29 +14,29 @@
:class="['category-item', activeCategoryId == cat.id ? 'active' : '']"
@click="selectCategory(cat.id)"
>
<view class="folder-icon">📂</view>
<view class="folder-icon">馃搨</view>
<text class="cat-name">{{ cat.name }}</text>
</view>
</scroll-view>
</view>
<!-- 右侧话术列表 -->
<!-- 鍙充晶璇濇湳鍒楄〃 -->
<view class="content-main">
<view class="top-bar">
<view class="btn-primary" @click="handleAddWord">
<text class="btn-txt">添加话术</text>
<text class="btn-txt">娣诲姞璇濇湳</text>
</view>
</view>
<view class="table-card border-shadow">
<view class="table-header">
<view class="th col-id"><text class="th-txt">ID</text></view>
<view class="th col-cat"><text class="th-txt">分类</text></view>
<view class="th col-title"><text class="th-txt">标题</text></view>
<view class="th col-detail"><text class="th-txt">详情</text></view>
<view class="th col-sort"><text class="th-txt">排序</text></view>
<view class="th col-time"><text class="th-txt">添加时间</text></view>
<view class="th col-op"><text class="th-txt">操作</text></view>
<view class="th col-cat"><text class="th-txt">鍒嗙被</text></view>
<view class="th col-title"><text class="th-txt">鏍囬</text></view>
<view class="th col-detail"><text class="th-txt">璇︽儏</text></view>
<view class="th col-sort"><text class="th-txt">鎺掑簭</text></view>
<view class="th col-time"><text class="th-txt">娣诲姞鏃堕棿</text></view>
<view class="th col-op"><text class="th-txt">鎿嶄綔</text></view>
</view>
<view class="table-body">
@@ -50,19 +50,19 @@
<view class="td col-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
<view class="td col-op">
<text class="btn-action" @click="handleEdit(item)">编辑</text>
<text class="btn-action" @click="handleEdit(item)">缂栬緫</text>
<view class="divider"></view>
<text class="btn-action danger">删除</text>
<text class="btn-action danger">鍒犻櫎</text>
</view>
</view>
</view>
<!-- 分页 -->
<!-- 鍒嗛〉 -->
<view class="pagination-bar">
<text class="page-total">{{ wordList.length }} 条</text>
<text class="page-total">鍏?{{ wordList.length }} 鏉?/text>
<view class="page-size-select">
<text class="size-txt">15条/页</text>
<text class="arrow-down">▼</text>
<text class="size-txt">15鏉?椤?/text>
<text class="arrow-down">鈻?/text>
</view>
<view class="page-nav">
<view class="nav-prev"><text class="nav-icon"> < </text></view>
@@ -70,32 +70,32 @@
<view class="nav-next"><text class="nav-icon"> > </text></view>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<text class="jump-txt">鍓嶅線</text>
<input class="jump-input" value="1" />
<text class="jump-txt">页</text>
<text class="jump-txt">椤?/text>
</view>
</view>
</view>
</view>
</view>
<!-- 侧边弹窗 (Drawer) -->
<!-- 渚ц竟寮圭獥 (Drawer) -->
<view v-if="showDrawer" class="drawer-mask" @click="closeDrawer">
<view class="drawer-content" @click.stop>
<view class="drawer-header">
<text class="drawer-title">{{ isEdit ? '编辑话术' : '添加话术' }}</text>
<text class="close-btn" @click="closeDrawer">×</text>
<text class="drawer-title">{{ isEdit ? '缂栬緫璇濇湳' : '娣诲姞璇濇湳' }}</text>
<text class="close-btn" @click="closeDrawer"></text>
</view>
<view class="drawer-body">
<view class="form-item">
<view class="label-box">
<text class="label-txt">话术分类:</text>
<text class="label-txt">璇濇湳鍒嗙被:</text>
</view>
<view class="input-box">
<view class="select-mock">
<text class="select-val">{{ formData.category || '请选择分类' }}</text>
<text class="arrow-down">▼</text>
<text class="select-val">{{ formData.category || '璇烽€夋嫨鍒嗙被' }}</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
</view>
@@ -103,26 +103,26 @@
<view class="form-item">
<view class="label-box">
<text class="required">*</text>
<text class="label-txt">话术标题:</text>
<text class="label-txt">璇濇湳鏍囬:</text>
</view>
<view class="input-box">
<input class="input-base" v-model="formData.title" placeholder="请输入话术标题" />
<input class="input-base" v-model="formData.title" placeholder="璇疯緭鍏ヨ瘽鏈爣棰? />
</view>
</view>
<view class="form-item align-start">
<view class="label-box">
<text class="required">*</text>
<text class="label-txt">话术内容:</text>
<text class="label-txt">璇濇湳鍐呭:</text>
</view>
<view class="input-box">
<textarea class="textarea-base" v-model="formData.content" placeholder="请输入话术内容" />
<textarea class="textarea-base" v-model="formData.content" placeholder="璇疯緭鍏ヨ瘽鏈唴瀹? />
</view>
</view>
<view class="form-item">
<view class="label-box">
<text class="label-txt">排序:</text>
<text class="label-txt">鎺掑簭:</text>
</view>
<view class="input-box">
<input class="input-base" type="number" v-model="formData.sort" />
@@ -132,10 +132,10 @@
<view class="drawer-footer">
<view class="btn-cancel" @click="closeDrawer">
<text class="cancel-txt">取消</text>
<text class="cancel-txt">鍙栨秷</text>
</view>
<view class="btn-confirm" @click="submitForm">
<text class="confirm-txt">确定</text>
<text class="confirm-txt">纭畾</text>
</view>
</view>
</view>
@@ -161,8 +161,8 @@ interface Category {
}
const categories = ref<Category[]>([
{ id: 1, name: '全部' },
{ id: 2, name: '系统话术' }
{ id: 1, name: '鍏ㄩ儴' },
{ id: 2, name: '绯荤粺璇濇湳' }
])
const activeCategoryId = ref(1)
@@ -170,20 +170,20 @@ const activeCategoryId = ref(1)
const wordList = ref<WordItem[]>([
{
id: '67',
category: '系统默认',
title: '旗舰版介绍',
content: '【旗舰版】可以授权给公司或者个人,企业自用搭建,不限制授权域名,提供专属技术总监、产品总监等。',
category: '绯荤粺榛樿',
title: '鏃楄埌鐗堜粙缁?,
content: '銆愭棗鑸扮増銆戝彲浠ユ巿鏉冪粰鍏徃鎴栬€呬釜浜猴紝浼佷笟鑷敤鎼缓锛屼笉闄愬埗鎺堟潈鍩熷悕锛屾彁渚涗笓灞炴妧鏈€荤洃銆佷骇鍝佹€荤洃绛夈€?,
sort: 0,
time: '2024-09-27 10:15:07'
}
])
// 表单逻辑
// 琛ㄥ崟閫昏緫
const showDrawer = ref(false)
const isEdit = ref(false)
const formData = reactive({
id: '',
category: '系统话术',
category: '绯荤粺璇濇湳',
title: '',
content: '',
sort: 0
@@ -194,7 +194,7 @@ const selectCategory = (id: number) => {
}
const handleAddCategory = () => {
console.log('添加分类')
console.log('娣诲姞鍒嗙被')
}
const handleAddWord = () => {
@@ -221,7 +221,7 @@ const closeDrawer = () => {
}
const submitForm = () => {
console.log('提交表单', formData)
console.log('鎻愪氦琛ㄥ崟', formData)
showDrawer.value = false
}
</script>
@@ -246,7 +246,7 @@ const submitForm = () => {
gap: 20px;
}
/* 左侧分类 */
/* 宸︿晶鍒嗙被 */
.category-sidebar {
width: 280px;
display: flex;
@@ -307,7 +307,7 @@ const submitForm = () => {
color: #333;
}
/* 右侧内容 */
/* 鍙充晶鍐呭 */
.content-main {
flex: 1;
display: flex;
@@ -395,7 +395,7 @@ const submitForm = () => {
text-align: left;
}
/* 列宽分配 */
/* 鍒楀鍒嗛厤 */
.col-id { width: 60px; }
.col-cat { width: 100px; }
.col-title { width: 150px; }
@@ -421,7 +421,7 @@ const submitForm = () => {
margin: 0 10px;
}
/* 分页 */
/* 鍒嗛〉 */
.pagination-bar {
padding: 15px 20px;
display: flex;
@@ -478,7 +478,7 @@ const submitForm = () => {
font-size: 13px;
}
/* 抽屉 Drawer 1:1 复刻 - 修正位置到右侧 */
/* 鎶藉眽 Drawer 1:1 澶嶅埢 - 淇浣嶇疆鍒板彸渚?*/
.drawer-mask {
position: fixed;
top: 0;
@@ -492,10 +492,10 @@ const submitForm = () => {
.drawer-content {
position: absolute;
top: 0;
right: 0; /* 强制靠右占据右边屏幕 */
width: 50%; /* 占屏幕一半宽度 */
right: 0; /* 寮哄埗闈犲彸鍗犳嵁鍙宠竟灞忓箷 */
width: 50%; /* 鍗犲睆骞曚竴鍗婂搴?*/
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
@@ -624,3 +624,4 @@ const submitForm = () => {
.cancel-txt { font-size: 14px; color: #606266; }
.confirm-txt { font-size: 14px; color: #fff; }
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">砍价活动</text>
<text class="page-title">鐮嶄环娲诲姩</text>
<text class="page-subtitle">Component: MarketingBargain</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 砍价活动 的具体功能
// TODO: 瀹炵幇 鐮嶄环娲诲姩 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="marketing-combination-create">
<view class="page-header">
<view class="back-btn" @click="handleBack">
<text class="back-ic">←</text>
<text class="back-txt">返回</text>
<text class="back-ic">鈫?/text>
<text class="back-txt">杩斿洖</text>
</view>
<text class="page-title">{{ isEdit ? '编辑拼团商品' : '添加拼团商品' }}</text>
<text class="page-title">{{ isEdit ? '缂栬緫鎷煎洟鍟嗗搧' : '娣诲姞鎷煎洟鍟嗗搧' }}</text>
</view>
<view class="steps-card border-shadow">
@@ -13,7 +13,7 @@
<view v-for="(step, index) in steps" :key="index" :class="['step-item', currentStep >= index ? 'active' : '']">
<view class="step-icon">
<text class="step-num" v-if="currentStep <= index">{{ index + 1 }}</text>
<text class="step-check" v-else>✓</text>
<text class="step-check" v-else>鉁?/text>
</view>
<text class="step-text">{{ step }}</text>
<view class="step-line" v-if="index < steps.length - 1"></view>
@@ -21,82 +21,82 @@
</view>
</view>
<!-- 步骤1: 选择商品 -->
<!-- 姝ラ1: 閫夋嫨鍟嗗搧 -->
<view v-if="currentStep === 0" class="step-content border-shadow">
<view class="form-item row-center">
<text class="label required">选择商品:</text>
<text class="label required">閫夋嫨鍟嗗搧锛?/text>
<view class="goods-selector" @click="openGoodsSelector">
<view v-if="selectedGood" class="selected-inner">
<image class="good-img" :src="selectedGood.image" mode="aspectFill"></image>
<text class="good-name">{{ selectedGood.title }}</text>
</view>
<view v-else class="selector-empty">
<text class="empty-ic">🛍️</text>
<text class="empty-txt">选择商品</text>
<text class="empty-ic">馃泹锔?/text>
<text class="empty-txt">閫夋嫨鍟嗗搧</text>
</view>
</view>
</view>
</view>
<!-- 步骤2: 填写基础信息 -->
<!-- 姝ラ2: 濉啓鍩虹淇℃伅 -->
<view v-if="currentStep === 1" class="step-content border-shadow">
<view class="form-scroll">
<view class="form-section">
<text class="section-title">基础配置</text>
<text class="section-title">鍩虹閰嶇疆</text>
<view class="form-item">
<text class="label required">拼团名称:</text>
<input class="input" v-model="form.title" placeholder="请输入拼团名称" />
<text class="label required">鎷煎洟鍚嶇О锛?/text>
<input class="input" v-model="form.title" placeholder="璇疯緭鍏ユ嫾鍥㈠悕绉? />
</view>
<view class="form-item">
<text class="label required">拼团简介:</text>
<textarea class="textarea" v-model="form.info" placeholder="请输入拼团简介" />
<text class="label required">鎷煎洟绠€浠嬶細</text>
<textarea class="textarea" v-model="form.info" placeholder="璇疯緭鍏ユ嫾鍥㈢畝浠? />
</view>
<view class="form-item">
<text class="label required">拼团时间:</text>
<text class="label required">鎷煎洟鏃堕棿锛?/text>
<view class="date-range">
<input class="input-half" v-model="form.start_time" placeholder="开始日期" />
<input class="input-half" v-model="form.start_time" placeholder="寮€濮嬫棩鏈? />
<text class="range-sep">-</text>
<input class="input-half" v-model="form.stop_time" placeholder="结束日期" />
<input class="input-half" v-model="form.stop_time" placeholder="缁撴潫鏃ユ湡" />
</view>
</view>
</view>
<view class="form-section">
<text class="section-title">拼团规则</text>
<text class="section-title">鎷煎洟瑙勫垯</text>
<view class="form-item">
<text class="label required">拼团时效:</text>
<text class="label required">鎷煎洟鏃舵晥锛?/text>
<view class="input-unit">
<input class="input" type="number" v-model="form.effective_time" />
<text class="unit">小时</text>
<text class="unit">灏忔椂</text>
</view>
</view>
<view class="form-item">
<text class="label required">拼团人数:</text>
<text class="label required">鎷煎洟浜烘暟锛?/text>
<view class="input-unit">
<input class="input" type="number" v-model="form.people" />
<text class="unit">人</text>
<text class="unit">浜?/text>
</view>
</view>
<view class="form-item">
<text class="label">虚拟成团:</text>
<text class="label">铏氭嫙鎴愬洟锛?/text>
<view class="input-unit">
<input class="input" type="number" v-model="form.virtual_people" />
<text class="unit">人</text>
<text class="unit">浜?/text>
</view>
</view>
</view>
</view>
</view>
<!-- 步骤3: 修改商品详情 -->
<!-- 姝ラ3: 淇敼鍟嗗搧璇︽儏 -->
<view v-if="currentStep === 2" class="step-content border-shadow">
<view class="spec-table">
<view class="table-head">
<view class="th">规格</view>
<view class="th">拼团价</view>
<view class="th">成本价</view>
<view class="th">限量</view>
<view class="th">库存</view>
<view class="th">瑙勬牸</view>
<view class="th">鎷煎洟浠?/view>
<view class="th">鎴愭湰浠?/view>
<view class="th">闄愰噺</view>
<view class="th">搴撳瓨</view>
</view>
<view class="table-row" v-for="(spec, index) in specs" :key="index">
<view class="td">{{ spec.name }}</view>
@@ -108,25 +108,25 @@
</view>
<view class="detail-section">
<text class="label">商品详情:</text>
<text class="label">鍟嗗搧璇︽儏锛?/text>
<view class="editor-placeholder">
<text class="p-txt">这里是编辑器区域 (WangEditor 映射)</text>
<text class="p-txt">杩欓噷鏄紪杈戝櫒鍖哄煙 (WangEditor 鏄犲皠)</text>
</view>
</view>
</view>
<view class="footer-actions">
<view v-if="currentStep > 0" class="btn outline" @click="prevStep">上一步</view>
<view class="btn primary" @click="nextStep">{{ currentStep === 2 ? '提交' : '下一步' }}</view>
<view v-if="currentStep > 0" class="btn outline" @click="prevStep">涓婁竴姝?/view>
<view class="btn primary" @click="nextStep">{{ currentStep === 2 ? '鎻愪氦' : '涓嬩竴姝? }}</view>
</view>
<!-- 商品选择弹窗 -->
<!-- 鍟嗗搧閫夋嫨寮圭獥 -->
<view v-if="showSelector" class="modal">
<view class="modal-mask" @click="showSelector = false"></view>
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">选择商品</text>
<text class="close" @click="showSelector = false">✕</text>
<text class="modal-title">閫夋嫨鍟嗗搧</text>
<text class="close" @click="showSelector = false">鉁?/text>
</view>
<view class="modal-body">
<view v-for="g in goodsList" :key="g.id" class="good-item" @click="selectGood(g)">
@@ -142,7 +142,7 @@
<script setup lang="uts">
import { ref, reactive } from 'vue'
const steps = ['选择拼团商品', '填写基础信息', '修改商品详情']
const steps = ['閫夋嫨鎷煎洟鍟嗗搧', '濉啓鍩虹淇℃伅', '淇敼鍟嗗搧璇︽儏']
const currentStep = ref(0)
const isEdit = ref(false)
const showSelector = ref(false)
@@ -160,12 +160,12 @@ const form = reactive({
})
const specs = ref([
{ name: '默认规格', price: '0.01', cost: '0.00', quota: '100', stock: '999' }
{ name: '榛樿瑙勬牸', price: '0.01', cost: '0.00', quota: '100', stock: '999' }
])
const goodsList = ref([
{ id: 1, title: 'UR2024夏季新款女装...', image: 'https://img14.360buyimg.com/n1/jfs/t1/172605/32/17036/114175/609a473eE6997455c/df82c6168e36712b.jpg' },
{ id: 2, title: 'FOMIX 蛋壳椅...', image: 'https://img12.360buyimg.com/n1/jfs/t1/185449/19/11995/4379/60d96d27E6a877c8e/3c38d4e92a2a7a5a.jpg' }
{ id: 1, title: 'UR2024澶忓鏂版濂宠...', image: 'https://img14.360buyimg.com/n1/jfs/t1/172605/32/17036/114175/609a473eE6997455c/df82c6168e36712b.jpg' },
{ id: 2, title: 'FOMIX 铔嬪3妞?..', image: 'https://img12.360buyimg.com/n1/jfs/t1/185449/19/11995/4379/60d96d27E6a877c8e/3c38d4e92a2a7a5a.jpg' }
])
const handleBack = () => {
@@ -184,13 +184,13 @@ const selectGood = (g: any) => {
const nextStep = () => {
if (currentStep.value === 0 && !selectedGood.value) {
uni.showToast({ title: '请先选择商品', icon: 'none' })
uni.showToast({ title: '璇峰厛閫夋嫨鍟嗗搧', icon: 'none' })
return
}
if (currentStep.value < 2) {
currentStep.value++
} else {
uni.showToast({ title: '提交成功' })
uni.showToast({ title: '鎻愪氦鎴愬姛' })
setTimeout(() => uni.navigateBack(), 1500)
}
}
@@ -431,10 +431,11 @@ const prevStep = () => {
/* Modal */
.modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; }
.modal-mask { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.modal-content { position: relative; width: 600px; background: #fff; border-radius: 8px; padding: 20px; }
.modal-content { position: relative; width: 600px; background: #fff; border-radius: 8px; /* padding removed */ }
.modal-header { display: flex; justify-content: space-between; margin-bottom: 20px; }
.modal-body { display: flex; flex-direction: column; gap: 12px; }
.good-item { display: flex; flex-direction: row; align-items: center; padding: 10px; border-bottom: 1px solid #f2f2f2; cursor: pointer; }
.g-thumb { width: 40px; height: 40px; margin-right: 12px; }
.g-title { font-size: 14px; }
</style>

View File

@@ -1,23 +1,23 @@
<template>
<template>
<view class="admin-marketing-lottery-list">
<!-- 筛选区域 -->
<!-- 绛涢€夊尯鍩?-->
<view class="filter-card box-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">活动时间:</text>
<text class="label">娲诲姩鏃堕棿锛?/text>
<view class="date-range-mock">
<text class="date-text">开始日期 - 结束日期</text>
<text class="date-text">寮€濮嬫棩鏈?- 缁撴潫鏃ユ湡</text>
<text class="iconfont icon-calendar"></text>
</view>
</view>
<view class="filter-item">
<text class="label">活动状态:</text>
<text class="label">娲诲姩鐘舵€侊細</text>
<picker :range="statusOptions" range-key="label" @change="statusChange">
<view class="picker-value">{{ getStatusLabel(currentStatus) }} <text class="iconfont icon-arrow-down"></text></view>
</picker>
</view>
<view class="filter-item">
<text class="label">活动类型:</text>
<text class="label">娲诲姩绫诲瀷锛?/text>
<picker :range="typeOptions" range-key="label" @change="typeChange">
<view class="picker-value">{{ getTypeLabel(currentType) }} <text class="iconfont icon-arrow-down"></text></view>
</picker>
@@ -25,34 +25,34 @@
</view>
<view class="filter-row second-row">
<view class="filter-item">
<text class="label">搜索抽奖:</text>
<input class="admin-input" placeholder="请输入活动名称" v-model="searchQuery" style="width: 200px;" />
<text class="label">鎼滅储鎶藉锛?/text>
<input class="admin-input" placeholder="璇疯緭鍏ユ椿鍔ㄥ悕绉? v-model="searchQuery" style="width: 200px;" />
</view>
<view class="filter-item">
<button class="admin-btn admin-btn-primary search-btn" @click="handleSearch">搜索</button>
<button class="admin-btn admin-btn-primary search-btn" @click="handleSearch">鎼滅储</button>
</view>
</view>
</view>
<!-- 操作工具栏 -->
<!-- 鎿嶄綔宸ュ叿鏍?-->
<view class="table-toolbar">
<button class="admin-btn admin-btn-primary">创建抽奖活动</button>
<button class="admin-btn admin-btn-primary">鍒涘缓鎶藉娲诲姩</button>
</view>
<!-- 表格区域 -->
<!-- 琛ㄦ牸鍖哄煙 -->
<view class="table-card box-shadow">
<view class="table-header">
<text class="col-60 center">ID</text>
<text class="col-120">活动名称</text>
<text class="col-100">活动类型</text>
<text class="col-80 center">抽奖人数</text>
<text class="col-80 center">中奖人数</text>
<text class="col-80 center">抽奖次数</text>
<text class="col-80 center">中奖次数</text>
<text class="col-80 center">活动状态</text>
<text class="col-80 center">开启状态</text>
<text class="col-180">活动时间</text>
<text class="col-120 center">操作</text>
<text class="col-120">娲诲姩鍚嶇О</text>
<text class="col-100">娲诲姩绫诲瀷</text>
<text class="col-80 center">鎶藉浜烘暟</text>
<text class="col-80 center">涓浜烘暟</text>
<text class="col-80 center">鎶藉娆℃暟</text>
<text class="col-80 center">涓娆℃暟</text>
<text class="col-80 center">娲诲姩鐘舵€?/text>
<text class="col-80 center">寮€鍚姸鎬?/text>
<text class="col-180">娲诲姩鏃堕棿</text>
<text class="col-120 center">鎿嶄綔</text>
</view>
<view class="table-body">
@@ -74,31 +74,31 @@
</view>
<view class="col-180">
<view class="time-range">
<text class="time-label">开始: {{ item.startTime }}</text>
<text class="time-label">结束: {{ item.endTime }}</text>
<text class="time-label">寮€濮? {{ item.startTime }}</text>
<text class="time-label">缁撴潫: {{ item.endTime }}</text>
</view>
</view>
<view class="col-120 center ops-cell">
<text class="op-link">编辑</text>
<text class="op-link">抽奖记录</text>
<text class="op-link">更多 <text class="iconfont icon-arrow-down" style="font-size: 10px;"></text></text>
<text class="op-link">缂栬緫</text>
<text class="op-link">鎶藉璁板綍</text>
<text class="op-link">鏇村 <text class="iconfont icon-arrow-down" style="font-size: 10px;"></text></text>
</view>
</view>
</view>
<!-- 分页区域 -->
<!-- 鍒嗛〉鍖哄煙 -->
<view class="table-pagination">
<text class="total-text">{{ total }} 条</text>
<text class="total-text">鍏?{{ total }} 鏉?/text>
<view class="page-ops">
<picker :range="pageSizes" @change="pageSizeChange">
<view class="size-picker">{{ currentSize }}条/页 <text class="iconfont icon-arrow-down"></text></view>
<view class="size-picker">{{ currentSize }}鏉?椤?<text class="iconfont icon-arrow-down"></text></view>
</picker>
<button class="page-btn" disabled>上一页</button>
<button class="page-btn" disabled>涓婁竴椤?/button>
<text class="current-page">1</text>
<button class="page-btn" disabled>下一页</button>
<text class="jump-text">前往</text>
<button class="page-btn" disabled>涓嬩竴椤?/button>
<text class="jump-text">鍓嶅線</text>
<input class="jump-input" value="1" />
<text class="jump-text">页</text>
<text class="jump-text">椤?/text>
</view>
</view>
</view>
@@ -114,68 +114,68 @@ export default {
currentType: 0,
total: 4,
currentSize: 15,
pageSizes: ['15条/页', '30条/页', '50条/页'],
pageSizes: ['15鏉?椤?, '30鏉?椤?, '50鏉?椤?],
statusOptions: [
{ label: '全部', value: 0 },
{ label: '未开始', value: 1 },
{ label: '进行中', value: 2 },
{ label: '已结束', value: 3 }
{ label: '鍏ㄩ儴', value: 0 },
{ label: '鏈紑濮?, value: 1 },
{ label: '杩涜涓?, value: 2 },
{ label: '宸茬粨鏉?, value: 3 }
] as any[],
typeOptions: [
{ label: '全部', value: 0 },
{ label: '积分抽奖', value: 1 },
{ label: '订单评价', value: 2 },
{ label: '订单支付', value: 3 }
{ label: '鍏ㄩ儴', value: 0 },
{ label: '绉垎鎶藉', value: 1 },
{ label: '璁㈠崟璇勪环', value: 2 },
{ label: '璁㈠崟鏀粯', value: 3 }
] as any[],
tableData: [
{
id: 87,
name: '积分抽奖',
typeName: '积分抽取',
name: '绉垎鎶藉',
typeName: '绉垎鎶藉彇',
memberCount: 166,
winningMemberCount: 0,
lotteryTimes: 329,
winningTimes: 0,
statusText: '已结束',
statusText: '宸茬粨鏉?,
isOpen: true,
startTime: '2025-12-04 00:00:00',
endTime: '2026-01-31 23:59:59'
},
{
id: 86,
name: '评价抽奖',
typeName: '订单评价',
name: '璇勪环鎶藉',
typeName: '璁㈠崟璇勪环',
memberCount: 3,
winningMemberCount: 3,
lotteryTimes: 4,
winningTimes: 3,
statusText: '已结束',
statusText: '宸茬粨鏉?,
isOpen: true,
startTime: '2023-12-12 00:00:00',
endTime: '2024-01-16 23:59:59'
},
{
id: 82,
name: '订单抽奖',
typeName: '订单支付',
name: '璁㈠崟鎶藉',
typeName: '璁㈠崟鏀粯',
memberCount: 100,
winningMemberCount: 5,
lotteryTimes: 124,
winningTimes: 6,
statusText: '已结束',
statusText: '宸茬粨鏉?,
isOpen: true,
startTime: '2023-08-16 00:00:00',
endTime: '2024-01-31 23:59:59'
},
{
id: 75,
name: '积分',
typeName: '积分抽取',
name: '绉垎',
typeName: '绉垎鎶藉彇',
memberCount: 1288,
winningMemberCount: 1130,
lotteryTimes: 3413,
winningTimes: 2628,
statusText: '已结束',
statusText: '宸茬粨鏉?,
isOpen: true,
startTime: '2025-10-01 00:00:00',
endTime: '2025-11-30 23:59:59'
@@ -186,11 +186,11 @@ export default {
methods: {
getStatusLabel(val : number) : string {
const found = this.statusOptions.find((item : any) : boolean => item.value == val)
return found != null ? (found['label'] as string) : '全部'
return found != null ? (found['label'] as string) : '鍏ㄩ儴'
},
getTypeLabel(val : number) : string {
const found = this.typeOptions.find((item : any) : boolean => item.value == val)
return found != null ? (found['label'] as string) : '全部'
return found != null ? (found['label'] as string) : '鍏ㄩ儴'
},
statusChange(e : any) {
this.currentStatus = this.statusOptions[e.detail.value].value
@@ -200,14 +200,14 @@ export default {
},
pageSizeChange(e : any) {
const val = this.pageSizes[e.detail.value]
this.currentSize = parseInt(val.replace('条/页', ''))
this.currentSize = parseInt(val.replace('鏉?椤?, ''))
},
handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
},
toggleStatus(item : any) {
item.isOpen = !item.isOpen
uni.showToast({ title: '状态已变更', icon: 'none' })
uni.showToast({ title: '鐘舵€佸凡鍙樻洿', icon: 'none' })
}
}
}
@@ -227,7 +227,7 @@ export default {
margin-bottom: 16px;
}
/* 筛选区域 */
/* 绛涢€夊尯鍩?*/
.filter-card {
padding: 15px 20px;
}
@@ -293,12 +293,12 @@ export default {
margin-left: 10px;
}
/* 表格工具栏 */
/* 琛ㄦ牸宸ュ叿鏍?*/
.table-toolbar {
margin-bottom: 16px;
}
/* 表格主体 */
/* 琛ㄦ牸涓讳綋 */
.table-card {
overflow: hidden;
}
@@ -322,7 +322,7 @@ export default {
padding: 12px 0;
}
/* 列定义 */
/* 鍒楀畾涔?*/
.col-60 { width: 60px; }
.col-80 { width: 80px; }
.col-100 { width: 100px; }
@@ -334,7 +334,7 @@ export default {
.id-text { font-size: 13px; color: #808695; }
.status-text { font-size: 13px; color: #515a6e; }
/* 开关组件 */
/* 寮€鍏崇粍浠?*/
.switch-box {
width: 40px;
height: 20px;
@@ -382,7 +382,7 @@ export default {
cursor: pointer;
}
/* 分页 */
/* 鍒嗛〉 */
.table-pagination {
padding: 16px 20px;
display: flex;
@@ -424,7 +424,7 @@ export default {
height: 28px;
line-height: 28px;
text-align: center;
background-color: #2d8cf0;
color: #fff;
font-size: 14px;
border-radius: 2px;
@@ -443,3 +443,4 @@ export default {

View File

@@ -1,86 +1,86 @@
<template>
<template>
<view class="admin-main">
<view class="card-container">
<view class="card-header">
<text class="card-header-title">新人礼设置</text>
<text class="card-header-title">鏂颁汉绀艰缃?/text>
</view>
<view class="form-content">
<!-- 赠送余额 -->
<!-- 璧犻€佷綑棰?-->
<view class="form-item">
<view class="label-col">
<text class="form-label">赠送余额(元):</text>
<text class="form-label">璧犻€佷綑棰?鍏?:</text>
</view>
<view class="input-col">
<input type="number" class="form-input" v-model="formData.balance" />
<text class="form-tip">新用户奖励金额必须大于等于00为不赠送</text>
<text class="form-tip">鏂扮敤鎴峰鍔遍噾棰濓紝蹇呴』澶т簬绛変簬0锛?涓轰笉璧犻€?/text>
</view>
</view>
<!-- 赠送积分 -->
<!-- 璧犻€佺Н鍒?-->
<view class="form-item">
<view class="label-col">
<text class="form-label">赠送积分:</text>
<text class="form-label">璧犻€佺Н鍒?</text>
</view>
<view class="input-col">
<input type="number" class="form-input" v-model="formData.integral" />
<text class="form-tip">新用户奖励积分必须大于等于00为不赠送</text>
<text class="form-tip">鏂扮敤鎴峰鍔辩Н鍒嗭紝蹇呴』澶т簬绛変簬0锛?涓轰笉璧犻€?/text>
</view>
</view>
<!-- 赠送优惠券 -->
<!-- 璧犻€佷紭鎯犲埜 -->
<view class="form-item row-center">
<view class="label-col">
<text class="form-label">赠送优惠券:</text>
<text class="form-label">璧犻€佷紭鎯犲埜:</text>
</view>
<view class="input-col row-layout">
<view class="coupon-display-area" v-if="formData.coupons.length > 0">
<view v-for="(coupon, index) in formData.coupons" :key="index" class="coupon-tag-group">
<view class="coupon-tag-main">
<text class="coupon-tag-name">{{ coupon.name }}</text>
<text class="coupon-tag-del" @click="removeCoupon(index)">×</text>
<text class="coupon-tag-del" @click="removeCoupon(index)"></text>
</view>
<view class="explicit-edit-btn" @click="editCoupon(index)">
<text class="edit-btn-text">修改设置</text>
<text class="edit-btn-text">淇敼璁剧疆</text>
</view>
</view>
</view>
<button class="btn-select-action" @click="showCouponModal = true">选择优惠券</button>
<button class="btn-select-action" @click="showCouponModal = true">閫夋嫨浼樻儬鍒?/button>
</view>
</view>
<!-- 确认按钮 -->
<!-- 纭鎸夐挳 -->
<view class="form-submit-bar">
<button class="btn-primary-confirm" @click="handleSubmit">确认</button>
<button class="btn-primary-confirm" @click="handleSubmit">纭</button>
</view>
</view>
</view>
<!-- 优惠券选择与详情配置弹窗 -->
<!-- 浼樻儬鍒搁€夋嫨涓庤鎯呴厤缃脊绐?-->
<view class="modal-mask" v-if="showCouponModal" @click="closeModal">
<view class="modal-box" @click.stop>
<view class="modal-head">
<text class="modal-head-title">{{ isEditing ? '配置赠送详情' : '选择优惠券' }}</text>
<text class="modal-head-close" @click="closeModal">×</text>
<text class="modal-head-title">{{ isEditing ? '閰嶇疆璧犻€佽鎯? : '閫夋嫨浼樻儬鍒? }}</text>
<text class="modal-head-close" @click="closeModal"></text>
</view>
<view class="modal-body">
<!-- 编辑/设置模式:当用户由于点击“修改设置”时触发 -->
<!-- 缂栬緫/璁剧疆妯″紡锛氬綋鐢ㄦ埛鐢变簬鐐瑰嚮鈥滀慨鏀硅缃€濇椂瑙﹀彂 -->
<view v-if="isEditing" class="setting-form">
<view class="setting-row">
<text class="setting-label">显示名称:</text>
<input class="setting-input" v-model="editingCoupon.name" placeholder="请输入页面显示的名称" />
<text class="setting-label">鏄剧ず鍚嶇О:</text>
<input class="setting-input" v-model="editingCoupon.name" placeholder="璇疯緭鍏ラ〉闈㈡樉绀虹殑鍚嶇О" />
</view>
<view class="setting-row">
<text class="setting-label">发放描述:</text>
<input class="setting-input" v-model="editingCoupon.desc" placeholder="请输入发放时的描述" />
<text class="setting-label">鍙戞斁鎻忚堪:</text>
<input class="setting-input" v-model="editingCoupon.desc" placeholder="璇疯緭鍏ュ彂鏀炬椂鐨勬弿杩? />
</view>
<view class="setting-tip">
<text class="tip-text">* 此处的修改仅影响“新人礼”活动中的展示,不影响优惠券自身配置</text>
<text class="tip-text">* 姝ゅ鐨勪慨鏀逛粎褰卞搷鈥滄柊浜虹ぜ鈥濇椿鍔ㄤ腑鐨勫睍绀猴紝涓嶅奖鍝嶄紭鎯犲埜鑷韩閰嶇疆</text>
</view>
</view>
<!-- 选择模式 -->
<!-- 閫夋嫨妯″紡 -->
<view v-else class="selection-list">
<view v-for="(item, index) in couponOptions" :key="index"
class="selection-card" :class="{'selected-card': isSelected(item)}"
@@ -91,7 +91,7 @@
</view>
<view class="card-right">
<view class="check-circle" :class="{'checked-circle': isSelected(item)}">
<text class="check-mark" v-if="isSelected(item)">✓</text>
<text class="check-mark" v-if="isSelected(item)">鉁?/text>
</view>
</view>
</view>
@@ -99,8 +99,8 @@
</view>
<view class="modal-foot">
<button class="foot-btn-cancel" @click="closeModal">取消</button>
<button class="foot-btn-ok" @click="confirmModal">确定</button>
<button class="foot-btn-cancel" @click="closeModal">鍙栨秷</button>
<button class="foot-btn-ok" @click="confirmModal">纭畾</button>
</view>
</view>
</view>
@@ -128,9 +128,9 @@ const editingIndex = ref(-1)
const editingCoupon = reactive<Coupon>({ id: 0, name: '', desc: '' })
const couponOptions = reactive<Coupon[]>([
{ id: 1, name: '满100减10元券', desc: '全场通用' },
{ id: 2, name: '新人5元无门槛', desc: '仅限新人使用' },
{ id: 3, name: '满200减50元券', desc: '限特定商品' }
{ id: 1, name: '婊?00鍑?0鍏冨埜', desc: '鍏ㄥ満閫氱敤' },
{ id: 2, name: '鏂颁汉5鍏冩棤闂ㄦ', desc: '浠呴檺鏂颁汉浣跨敤' },
{ id: 3, name: '婊?00鍑?0鍏冨埜', desc: '闄愮壒瀹氬晢鍝? }
])
function isSelected(item: Coupon): boolean {
@@ -174,11 +174,11 @@ function confirmModal() {
}
function handleSubmit() {
uni.showLoading({ title: '保存中...' })
uni.showLoading({ title: '淇濆瓨涓?..' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '设置已生效',
title: '璁剧疆宸茬敓鏁?,
icon: 'success'
})
}, 500)
@@ -193,7 +193,7 @@ function handleSubmit() {
}
.card-container {
background-color: #fff;
border-radius: 2px;
}
@@ -519,3 +519,4 @@ function handleSubmit() {
</style>

View File

@@ -1,49 +1,49 @@
<template>
<template>
<view class="admin-cashier-order">
<view class="content-body">
<!-- 顶部过滤栏 -->
<!-- 椤堕儴杩囨护鏍?-->
<view class="filter-card border-shadow">
<view class="filter-item">
<text class="label-txt">创建时间:</text>
<text class="label-txt">鍒涘缓鏃堕棿:</text>
<view class="date-picker-mock">
<text class="date-txt">开始日期</text>
<text class="date-txt">寮€濮嬫棩鏈?/text>
<text class="date-split">-</text>
<text class="date-txt">结束日期</text>
<text class="calendar-ic">📅</text>
<text class="date-txt">缁撴潫鏃ユ湡</text>
<text class="calendar-ic">馃搮</text>
</view>
</view>
<view class="filter-item">
<text class="label-txt">订单号:</text>
<input class="search-input" placeholder="请输入订单号" v-model="orderId" />
<text class="label-txt">璁㈠崟鍙?</text>
<input class="search-input" placeholder="璇疯緭鍏ヨ鍗曞彿" v-model="orderId" />
</view>
<view class="filter-item">
<text class="label-txt">用户名:</text>
<input class="search-input" placeholder="请输入用户名" v-model="username" />
<text class="label-txt">鐢ㄦ埛鍚?</text>
<input class="search-input" placeholder="璇疯緭鍏ョ敤鎴峰悕" v-model="username" />
</view>
<view class="btn-query" @click="handleQuery">
<text class="query-txt">查询</text>
<text class="query-txt">鏌ヨ</text>
</view>
</view>
<!-- 主要内容区域 -->
<!-- 涓昏鍐呭鍖哄煙 -->
<view class="table-card border-shadow">
<view class="card-header">
<view class="btn-primary-blue" @click="openQrModal">
<text class="btn-txt">查看收款二维码</text>
<text class="btn-txt">鏌ョ湅鏀舵浜岀淮鐮?/text>
</view>
</view>
<!-- 数据表格 -->
<!-- 鏁版嵁琛ㄦ牸 -->
<view class="table-container">
<view class="table-header-row">
<view class="th" style="flex: 1.5;">订单号</view>
<view class="th" style="flex: 1.2;">用户信息</view>
<view class="th" style="width: 150px;">实际支付</view>
<view class="th" style="width: 150px;">优惠价格</view>
<view class="th" style="width: 200px;">支付时间</view>
<view class="th" style="flex: 1.5;">璁㈠崟鍙?/view>
<view class="th" style="flex: 1.2;">鐢ㄦ埛淇℃伅</view>
<view class="th" style="width: 150px;">瀹為檯鏀粯</view>
<view class="th" style="width: 150px;">浼樻儬浠锋牸</view>
<view class="th" style="width: 200px;">鏀粯鏃堕棿</view>
</view>
<view class="table-body">
@@ -57,13 +57,13 @@
</view>
</view>
<!-- 分页 -->
<!-- 鍒嗛〉 -->
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">{{ total }} 条</text>
<text class="total-txt">鍏?{{ total }} 鏉?/text>
</view>
<view class="page-select">
<text class="page-val">15条/页 ▼</text>
<text class="page-val">15鏉?椤?鈻?/text>
</view>
<view class="page-btns">
<text class="p-btn disabled"><</text>
@@ -71,31 +71,31 @@
<text class="p-btn">></text>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<text class="jump-txt">鍓嶅線</text>
<input class="jump-input" placeholder="1" />
<text class="jump-txt">页</text>
<text class="jump-txt">椤?/text>
</view>
</view>
</view>
</view>
<!-- 收款码弹窗 -->
<!-- 鏀舵鐮佸脊绐?-->
<view v-if="showQrModal" :class="['modal-mask', isClosing ? 'mask-fade-out' : '']" @click="closeQrModal">
<view :class="['modal-content', isClosing ? 'scale-out' : 'scale-in']" @click.stop="">
<view class="modal-header">
<text class="modal-title">收款码</text>
<text class="close-btn" @click="closeQrModal">×</text>
<text class="modal-title">鏀舵鐮?/text>
<text class="close-btn" @click="closeQrModal"></text>
</view>
<view class="modal-body">
<view class="qr-item">
<image class="qr-img" src="https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg" mode="aspectFit"></image>
<text class="qr-label">公众号二维码</text>
<text class="qr-label">鍏紬鍙蜂簩缁寸爜</text>
</view>
<view class="qr-item">
<view class="qr-placeholder-mp">
<image class="mp-qr-mock" src="https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg" mode="aspectFit"></image>
</view>
<text class="qr-label">小程序二维码</text>
<text class="qr-label">灏忕▼搴忎簩缁寸爜</text>
</view>
</view>
</view>
@@ -118,14 +118,14 @@ const orderId = ref('')
const username = ref('')
const total = ref(12)
const orderList = ref<CashierOrder[]>([
{ orderId: 'hy536720518414336000', userInfo: '东流 | 76058', payPrice: 1.00, discountPrice: 0.00, payTime: '2026-01-21 01:35:43' },
{ orderId: 'hy529509398574268416', userInfo: '半个栗子 | 81997', payPrice: 10000.00, discountPrice: 0.00, payTime: '2026-01-01 04:01:18' },
{ orderId: 'hy511477797936431104', userInfo: '莉莉 | 80736', payPrice: 10.00, discountPrice: 0.00, payTime: '2025-11-12 09:50:09' },
{ orderId: 'hy536720518414336000', userInfo: '涓滄祦 | 76058', payPrice: 1.00, discountPrice: 0.00, payTime: '2026-01-21 01:35:43' },
{ orderId: 'hy529509398574268416', userInfo: '鍗婁釜鏍楀瓙 | 81997', payPrice: 10000.00, discountPrice: 0.00, payTime: '2026-01-01 04:01:18' },
{ orderId: 'hy511477797936431104', userInfo: '鑾夎帀 | 80736', payPrice: 10.00, discountPrice: 0.00, payTime: '2025-11-12 09:50:09' },
{ orderId: 'hy507152261823070208', userInfo: '. . . | 71546', payPrice: 0.10, discountPrice: 0.00, payTime: '2025-10-31 11:22:01' },
{ orderId: 'hy506826113427701760', userInfo: 'A| 80363', payPrice: 0.10, discountPrice: 0.00, payTime: '2025-10-30 13:46:01' },
{ orderId: 'hy504707908026499072', userInfo: '前前前前 | 80225', payPrice: 2252.00, discountPrice: 0.00, payTime: '2025-10-24 17:29:02' },
{ orderId: 'hy494505602832138240', userInfo: '张总说 | 40028', payPrice: 1.00, discountPrice: 0.00, payTime: '2025-09-26 13:48:43' },
{ orderId: 'hy490819329198129152', userInfo: '虚伪 | 79027', payPrice: 10.00, discountPrice: 0.00, payTime: '2025-09-16 09:40:47' }
{ orderId: 'hy506826113427701760', userInfo: 'A姊?| 80363', payPrice: 0.10, discountPrice: 0.00, payTime: '2025-10-30 13:46:01' },
{ orderId: 'hy504707908026499072', userInfo: '鍓嶅墠鍓嶅墠 | 80225', payPrice: 2252.00, discountPrice: 0.00, payTime: '2025-10-24 17:29:02' },
{ orderId: 'hy494505602832138240', userInfo: '寮犳€昏 | 40028', payPrice: 1.00, discountPrice: 0.00, payTime: '2025-09-26 13:48:43' },
{ orderId: 'hy490819329198129152', userInfo: '铏氫吉 | 79027', payPrice: 10.00, discountPrice: 0.00, payTime: '2025-09-16 09:40:47' }
])
const showQrModal = ref(false)
@@ -166,7 +166,7 @@ const closeQrModal = () => {
gap: 20px;
}
/* 过滤栏 */
/* 杩囨护鏍?*/
.filter-card {
padding: 24px;
display: flex;
@@ -221,7 +221,7 @@ const closeQrModal = () => {
}
.query-txt { color: #fff; font-size: 14px; }
/* 表格区域 */
/* 琛ㄦ牸鍖哄煙 */
.table-card {
background-color: #fff;
display: flex;
@@ -272,7 +272,7 @@ const closeQrModal = () => {
.td-txt { font-size: 14px; color: #515a6e; }
/* 分页 */
/* 鍒嗛〉 */
.pagination-footer {
padding: 24px;
display: flex;
@@ -301,7 +301,7 @@ const closeQrModal = () => {
.jump-txt { font-size: 14px; color: #606266; }
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
/* Modal 弹窗逻辑 */
/* Modal 寮圭獥閫昏緫 */
.modal-mask {
position: fixed;
top: 0;
@@ -317,7 +317,7 @@ const closeQrModal = () => {
.modal-content {
width: 600px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
@@ -371,7 +371,7 @@ const closeQrModal = () => {
.qr-label { font-size: 14px; color: #666; }
/* 动画 */
/* 鍔ㄧ敾 */
.scale-in { animation: scaleIn 0.3s ease-out forwards; }
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
@@ -392,3 +392,4 @@ const closeQrModal = () => {
</style>

View File

@@ -1,49 +1,49 @@
<template>
<template>
<view class="order-list-page">
<!-- 筛选区域 -->
<!-- 绛涢€夊尯鍩?-->
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">订单类型:</text>
<text class="label">璁㈠崟绫诲瀷锛?/text>
<view class="mock-select">
<text>全部订单</text>
<text>鍏ㄩ儴璁㈠崟</text>
<view class="arrow-down"></view>
</view>
</view>
<view class="filter-item">
<text class="label">支付方式:</text>
<text class="label">鏀粯鏂瑰紡锛?/text>
<view class="mock-select">
<text>全部</text>
<text>鍏ㄩ儴</text>
<view class="arrow-down"></view>
</view>
</view>
<view class="filter-item long">
<text class="label">创建时间:</text>
<text class="label">鍒涘缓鏃堕棿锛?/text>
<view class="mock-date-range">
<image class="cal-icon" src="/static/icons/calendar.png" mode="aspectFit" />
<text class="placeholder">开始日期 - 结束日期</text>
<text class="placeholder">寮€濮嬫棩鏈? - 缁撴潫鏃ユ湡</text>
</view>
</view>
<view class="filter-item search">
<text class="label">订单搜索:</text>
<text class="label">璁㈠崟鎼滅储锛?/text>
<view class="search-group">
<view class="search-select">
<text>全部</text>
<text>鍏ㄩ儴</text>
<view class="arrow-down"></view>
</view>
<input class="search-input" placeholder="请输入" />
<input class="search-input" placeholder="璇疯緭鍏? />
</view>
</view>
</view>
<view class="btn-row">
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-primary">鏌ヨ</button>
<button class="btn btn-default">閲嶇疆</button>
</view>
</view>
<!-- 列表数据区域 -->
<!-- 鍒楄〃鏁版嵁鍖哄煙 -->
<view class="content-card">
<!-- 状态 Tabs -->
<!-- 鐘舵€?Tabs -->
<view class="status-tabs">
<view
v-for="(tab, index) in statusTabs"
@@ -57,28 +57,28 @@
</view>
</view>
<!-- 操作按钮 -->
<!-- 鎿嶄綔鎸夐挳 -->
<view class="action-bar">
<button class="action-btn btn-blue">订单核销</button>
<button class="action-btn btn-outline">批量发货</button>
<button class="action-btn btn-outline">批量删除</button>
<button class="action-btn btn-outline">订单导出</button>
<button class="action-btn btn-blue">璁㈠崟鏍搁攢</button>
<button class="action-btn btn-outline">鎵归噺鍙戣揣</button>
<button class="action-btn btn-outline">鎵归噺鍒犻櫎</button>
<button class="action-btn btn-outline">璁㈠崟瀵煎嚭</button>
</view>
<!-- 数据表格 -->
<!-- 鏁版嵁琛ㄦ牸 -->
<view class="order-table">
<view class="thead">
<view class="th col-check">
<checkbox :checked="false" color="#1890ff" />
</view>
<view class="th col-order">订单号 | 类型</view>
<view class="th col-product">商品信息</view>
<view class="th col-user">用户信息</view>
<view class="th col-price">实际支付</view>
<view class="th col-pay">支付方式</view>
<view class="th col-time">支付时间</view>
<view class="th col-status">订单状态</view>
<view class="th col-op">操作</view>
<view class="th col-order">璁㈠崟鍙?| 绫诲瀷</view>
<view class="th col-product">鍟嗗搧淇℃伅</view>
<view class="th col-user">鐢ㄦ埛淇℃伅</view>
<view class="th col-price">瀹為檯鏀粯</view>
<view class="th col-pay">鏀粯鏂瑰紡</view>
<view class="th col-time">鏀粯鏃堕棿</view>
<view class="th col-status">璁㈠崟鐘舵€?/view>
<view class="th col-op">鎿嶄綔</view>
</view>
<view class="tbody">
@@ -86,45 +86,45 @@
<view class="td col-check">
<checkbox :checked="false" color="#1890ff" />
</view>
<!-- 订单号|类型 -->
<!-- 璁㈠崟鍙穦绫诲瀷 -->
<view class="td col-order">
<text class="order-sn">{{ item.sn }}</text>
<text class="order-type" :class="item.typeColor">[{{ item.typeName }}]</text>
<text v-if="item.cancelStatus" class="cancel-text">{{ item.cancelStatus }}</text>
</view>
<!-- 商品信息 -->
<!-- 鍟嗗搧淇℃伅 -->
<view class="td col-product">
<view class="product-info-wrap">
<image class="p-img" :src="item.product.img" mode="aspectFill" />
<text class="p-name">{{ item.product.name }}</text>
</view>
</view>
<!-- 用户信息 -->
<!-- 鐢ㄦ埛淇℃伅 -->
<view class="td col-user">
<text class="u-info">{{ item.user.phone }} | {{ item.user.id }}</text>
</view>
<!-- 实际支付 -->
<!-- 瀹為檯鏀粯 -->
<view class="td col-price">
<text class="price-val">{{ item.actualPrice }}</text>
</view>
<!-- 支付方式 -->
<!-- 鏀粯鏂瑰紡 -->
<view class="td col-pay">
<text class="pay-text">{{ item.payMethod }}</text>
</view>
<!-- 支付时间 -->
<!-- 鏀粯鏃堕棿 -->
<view class="td col-time">
<text class="time-text">{{ item.payTime }}</text>
</view>
<!-- 订单状态 -->
<!-- 璁㈠崟鐘舵€?-->
<view class="td col-status">
<text class="status-text">{{ item.statusName }}</text>
</view>
<!-- 操作 -->
<!-- 鎿嶄綔 -->
<view class="td col-op">
<view class="op-links">
<text class="op-link primary" v-if="item.primaryAction">{{ item.primaryAction }}</text>
<view class="op-link-more">
<text class="more-text">更多</text>
<text class="more-text">鏇村</text>
<view class="arrow-down-blue"></view>
</view>
</view>
@@ -139,93 +139,93 @@
<script setup lang="uts">
import { ref } from 'vue'
const activeTab = ref(2) // 默认选中待发货或待核销(手动对齐截图)
const activeTab = ref(2) // 榛樿閫変腑寰呭彂璐ф垨寰呮牳閿€锛堟墜鍔ㄥ榻愭埅鍥撅級
const statusTabs = [
{ name: '全部', count: null },
{ name: '待支付', count: 793 },
{ name: '待发货', count: 3695 },
{ name: '待核销', count: null },
{ name: '待收货', count: null },
{ name: '待评价', count: null },
{ name: '已完成', count: null },
{ name: '已退款', count: null },
{ name: '已删除', count: null }
{ name: '鍏ㄩ儴', count: null },
{ name: '寰呮敮浠?, count: 793 },
{ name: '寰呭彂璐?, count: 3695 },
{ name: '寰呮牳閿€', count: null },
{ name: '寰呮敹璐?, count: null },
{ name: '寰呰瘎浠?, count: null },
{ name: '宸插畬鎴?, count: null },
{ name: '宸查€€娆?, count: null },
{ name: '宸插垹闄?, count: null }
]
const orderData = ref([
{
sn: 'cp541336970228400128',
typeName: '秒杀订单',
typeName: '绉掓潃璁㈠崟',
typeColor: 'blue',
cancelStatus: '用户已取消',
cancelStatus: '鐢ㄦ埛宸插彇娑?,
product: {
img: '/static/logo.png',
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室 超高清手机便携投影机 (4K超清 支持...'
name: '鐖卞鑹烘櫤鑳?濂囬亣LT01 鎶曞奖浠?瀹剁敤鍗у 瓒呴珮娓呮墜鏈轰究鎼烘姇褰辨満 (4K瓒呮竻 鏀寔...'
},
user: { phone: '188****4074', id: '82694' },
actualPrice: '未支付',
actualPrice: '鏈敮浠?,
payMethod: '--',
payTime: '--',
statusName: '未支付',
statusName: '鏈敮浠?,
primaryAction: ''
},
{
sn: 'cp541289248708362240',
typeName: '核销订单',
typeName: '鏍搁攢璁㈠崟',
typeColor: 'purple',
cancelStatus: '',
product: {
img: '/static/logo.png',
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水...'
name: '闃胯开杈炬柉瀹樼綉 adidas BBALL CAP COT 鐢峰コ璁粌杩愬姩甯藉瓙FQ5270 浼犲澧ㄦ按...'
},
user: { phone: '你就给', id: '82703' },
user: { phone: '浣犲氨缁?, id: '82703' },
actualPrice: '90.1',
payMethod: '余额支付',
payMethod: '浣欓鏀粯',
payTime: '2026-02-02 16:10:17',
statusName: '未核销',
primaryAction: '立即核销'
statusName: '鏈牳閿€',
primaryAction: '绔嬪嵆鏍搁攢'
},
{
sn: 'cp541268226856714240',
typeName: '普通订单',
typeName: '鏅€氳鍗?,
typeColor: 'green',
cancelStatus: '',
product: {
img: '/static/logo.png',
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060'
name: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060'
},
user: { phone: '王毅不睡了', id: '82689' },
actualPrice: '未支付',
user: { phone: '鐜嬫瘏涓嶇潯浜?, id: '82689' },
actualPrice: '鏈敮浠?,
payMethod: '--',
payTime: '--',
statusName: '未支付',
primaryAction: '编辑'
statusName: '鏈敮浠?,
primaryAction: '缂栬緫'
},
{
sn: 'cp541262080745930752',
typeName: '秒杀订单',
typeName: '绉掓潃璁㈠崟',
typeColor: 'blue',
cancelStatus: '',
product: {
img: '/static/logo.png',
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室 超高清手机便携投影机 (4K超清 支持...'
name: '鐖卞鑹烘櫤鑳?濂囬亣LT01 鎶曞奖浠?瀹剁敤鍗у 瓒呴珮娓呮墜鏈轰究鎼烘姇褰辨満 (4K瓒呮竻 鏀寔...'
},
user: { phone: '177****8361', id: '82697' },
actualPrice: '未支付',
actualPrice: '鏈敮浠?,
payMethod: '--',
payTime: '--',
statusName: '未支付',
primaryAction: '编辑'
statusName: '鏈敮浠?,
primaryAction: '缂栬緫'
}
])
</script>
<style scoped lang="scss">
.order-list-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
.filter-card {
@@ -389,7 +389,7 @@ const orderData = ref([
.btn-blue { background-color: #1890ff; color: #fff; border: none; }
.btn-outline { background-color: #fff; color: #595959; border: 1px solid #d9d9d9; }
/* 表格样式 */
/* 琛ㄦ牸鏍峰紡 */
.order-table {
width: 100%;
}
@@ -423,7 +423,7 @@ const orderData = ref([
justify-content: center;
}
/* 列宽控制 */
/* 鍒楀鎺у埗 */
.col-check { width: 50px; justify-content: center; align-items: center; }
.col-order { width: 220px; }
.col-product { flex: 1; }
@@ -434,7 +434,7 @@ const orderData = ref([
.col-status { width: 100px; }
.col-op { width: 120px; }
/* 单元格具体内容样式 */
/* 鍗曞厓鏍煎叿浣撳唴瀹规牱寮?*/
.order-sn { font-size: 13px; color: #262626; margin-bottom: 4px; }
.order-type { font-size: 12px; }
.blue { color: #1890ff; }
@@ -480,3 +480,4 @@ const orderData = ref([
border-top: 4px solid #1890ff;
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<template>
<view class="order-statistic-page">
<!-- 时间选择卡片 -->
<!-- 鏃堕棿閫夋嫨鍗$墖 -->
<view class="filter-card">
<view class="filter-item">
<text class="filter-label">时间选择:</text>
<text class="filter-label">鏃堕棿閫夋嫨锛?/text>
<view class="date-picker-mock">
<image class="calendar-icon" src="/static/icons/calendar.png" mode="aspectFit" />
<text class="date-range">2026/01/04 - 2026/02/02</text>
@@ -11,71 +11,71 @@
</view>
</view>
<!-- 数据汇总卡片 -->
<!-- 鏁版嵁姹囨€诲崱鐗?-->
<view class="stat-cards-row">
<!-- 订单量 -->
<!-- 璁㈠崟閲?-->
<view class="stat-card">
<view class="icon-wrap blue-bg">
<view class="custom-icon icon-order"></view>
</view>
<view class="stat-info">
<text class="stat-value">209</text>
<text class="stat-desc">订单量</text>
<text class="stat-desc">璁㈠崟閲?/text>
</view>
</view>
<!-- 订单销售额 -->
<!-- 璁㈠崟閿€鍞 -->
<view class="stat-card">
<view class="icon-wrap orange-bg">
<view class="custom-icon icon-money"></view>
</view>
<view class="stat-info">
<text class="stat-value">443254.62</text>
<text class="stat-desc">订单销售额</text>
<text class="stat-desc">璁㈠崟閿€鍞</text>
</view>
</view>
<!-- 退款订单数 -->
<!-- 閫€娆捐鍗曟暟 -->
<view class="stat-card">
<view class="icon-wrap green-bg">
<view class="custom-icon icon-refund"></view>
</view>
<view class="stat-info">
<text class="stat-value">0</text>
<text class="stat-desc">退款订单数</text>
<text class="stat-desc">閫€娆捐鍗曟暟</text>
</view>
</view>
<!-- 退款金额 -->
<!-- 閫€娆鹃噾棰?-->
<view class="stat-card last-card">
<view class="icon-wrap pink-bg">
<view class="custom-icon icon-refund-money"></view>
</view>
<view class="stat-info">
<text class="stat-value">0</text>
<text class="stat-desc">退款金额</text>
<text class="stat-desc">閫€娆鹃噾棰?/text>
</view>
</view>
</view>
<!-- 营业趋势图表 -->
<!-- 钀ヤ笟瓒嬪娍鍥捐〃 -->
<view class="chart-card">
<view class="card-header">
<text class="card-title">营业趋势</text>
<text class="card-title">钀ヤ笟瓒嬪娍</text>
</view>
<view class="chart-container">
<EChartsView :option="trendOption" class="trend-chart" />
</view>
</view>
<!-- 底部双图表区域 -->
<!-- 搴曢儴鍙屽浘琛ㄥ尯鍩?-->
<view class="bottom-charts-row">
<!-- 订单来源分析 -->
<!-- 璁㈠崟鏉ユ簮鍒嗘瀽 -->
<view class="bottom-chart-card">
<view class="card-header-row">
<text class="card-title">订单来源分析</text>
<text class="card-title">璁㈠崟鏉ユ簮鍒嗘瀽</text>
<view class="style-toggle">
<text class="toggle-text">切换样式</text>
<text class="toggle-text">鍒囨崲鏍峰紡</text>
</view>
</view>
<view class="pie-chart-container">
@@ -83,20 +83,20 @@
</view>
</view>
<!-- 订单类型分析 -->
<!-- 璁㈠崟绫诲瀷鍒嗘瀽 -->
<view class="bottom-chart-card">
<view class="card-header-row">
<text class="card-title">订单类型分析</text>
<text class="card-title">璁㈠崟绫诲瀷鍒嗘瀽</text>
<view class="style-toggle">
<text class="toggle-text">切换样式</text>
<text class="toggle-text">鍒囨崲鏍峰紡</text>
</view>
</view>
<view class="type-table-container">
<view class="table-header">
<text class="th-text col-id">序号</text>
<text class="th-text col-name">来源</text>
<text class="th-text col-money">金额</text>
<text class="th-text col-rate">占比率</text>
<text class="th-text col-id">搴忓彿</text>
<text class="th-text col-name">鏉ユ簮</text>
<text class="th-text col-money">閲戦</text>
<text class="th-text col-rate">鍗犳瘮鐜?/text>
</view>
<view class="table-body">
<view v-for="(item, index) in orderTypeData" :key="index" class="table-row">
@@ -121,16 +121,16 @@
import { ref, onMounted } from 'vue'
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
const title = ref<string>('订单统计')
const title = ref<string>('璁㈠崟缁熻')
const trendOption = ref<any>({})
const sourceOption = ref<any>({})
const orderTypeData = ref([
{ name: '普通订单', amount: '430986.62', rate: '97.23' },
{ name: '拼团订单', amount: '7127', rate: '1.60' },
{ name: '预售订单', amount: '4835', rate: '1.09' },
{ name: '秒杀订单', amount: '306', rate: '0.06' },
{ name: '砍价订单', amount: '0', rate: '0.00' }
{ name: '鏅€氳鍗?, amount: '430986.62', rate: '97.23' },
{ name: '鎷煎洟璁㈠崟', amount: '7127', rate: '1.60' },
{ name: '棰勫敭璁㈠崟', amount: '4835', rate: '1.09' },
{ name: '绉掓潃璁㈠崟', amount: '306', rate: '0.06' },
{ name: '鐮嶄环璁㈠崟', amount: '0', rate: '0.00' }
])
onMounted(() => {
@@ -140,7 +140,7 @@ onMounted(() => {
})
/**
* 转换 UTS 对象为纯 JS 对象,确保 ECharts 能正确解析
* 杞崲 UTS 瀵硅薄涓虹函 JS 瀵硅薄锛岀‘淇?ECharts 鑳芥纭В鏋?
*/
function toPlainObject(obj: any): any {
if (obj == null) return null
@@ -188,7 +188,7 @@ function initSourceChart() {
color: ['#1890ff', '#52c41a', '#597ef7', '#ffc53d', '#ff7875'],
series: [
{
name: '订单来源',
name: '璁㈠崟鏉ユ簮',
type: 'pie',
radius: ['45%', '70%'],
center: ['40%', '50%'],
@@ -202,8 +202,8 @@ function initSourceChart() {
show: true
},
data: [
{ value: 1048, name: '公众号' },
{ value: 735, name: '小程序' },
{ value: 1048, name: '鍏紬鍙? },
{ value: 735, name: '灏忕▼搴? },
{ value: 580, name: 'H5' },
{ value: 484, name: 'PC' },
{ value: 300, name: 'APP' }
@@ -236,7 +236,7 @@ function initTrendChart() {
}
},
legend: {
data: ['订单金额', '订单量', '退款金额', '退款订单量'],
data: ['璁㈠崟閲戦', '璁㈠崟閲?, '閫€娆鹃噾棰?, '閫€娆捐鍗曢噺'],
top: 10,
right: 'center',
icon: 'circle',
@@ -263,7 +263,7 @@ function initTrendChart() {
},
series: [
{
name: '订单金额',
name: '璁㈠崟閲戦',
type: 'line',
smooth: false,
symbol: 'circle',
@@ -273,19 +273,19 @@ function initTrendChart() {
data: orderAmount
},
{
name: '订单量',
name: '璁㈠崟閲?,
type: 'line',
itemStyle: { color: '#5ad8a6' },
data: dates.map((_ : string) : number => Math.floor(Math.random() * 20))
},
{
name: '退款金额',
name: '閫€娆鹃噾棰?,
type: 'line',
itemStyle: { color: '#ff9d4d' },
data: dates.map((_ : string) : number => 0)
},
{
name: '退款订单量',
name: '閫€娆捐鍗曢噺',
type: 'line',
itemStyle: { color: '#9270ca' },
data: dates.map((_ : string) : number => 0)
@@ -299,8 +299,8 @@ function initTrendChart() {
<style scoped lang="scss">
.order-statistic-page {
padding: 16px;
background-color: #f0f2f5;
/* padding removed */
min-height: 100%;
}
@@ -438,7 +438,7 @@ function initTrendChart() {
color: #595959;
}
/* 统一列宽与对齐方式 */
/* 缁熶竴鍒楀涓庡榻愭柟寮?*/
.col-id { width: 80px; text-align: center; }
.col-name { flex: 1; text-align: left; padding-left: 40px; }
.col-money { width: 180px; text-align: left; }
@@ -461,7 +461,7 @@ function initTrendChart() {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start; /* 改为左对齐,与表头对齐样式一致 */
justify-content: flex-start; /* 鏀逛负宸﹀榻愶紝涓庤〃澶村榻愭牱寮忎竴鑷?*/
}
.progress-wrap {
@@ -505,7 +505,7 @@ function initTrendChart() {
margin-right: 20px;
}
/* 颜色背景 - 1:1 匹配截图 */
/* 棰滆壊鑳屾櫙 - 1:1 鍖归厤鎴浘 */
.blue-bg {
background-color: #e6f7ff;
border: 2px solid #bae7ff;
@@ -541,7 +541,7 @@ function initTrendChart() {
margin-top: 4px;
}
/* 自定义图标 1:1 形状模拟 - 内部使用伪元素或形状模拟截图形状 */
/* 鑷畾涔夊浘鏍?1:1 褰㈢姸妯℃嫙 - 鍐呴儴浣跨敤浼厓绱犳垨褰㈢姸妯℃嫙鎴浘褰㈢姸 */
.custom-icon {
width: 24px;
height: 24px;
@@ -563,7 +563,7 @@ function initTrendChart() {
background-color: #faad14;
border-radius: 50%;
&::after {
content: '¥';
content: '锟?;
color: #fff;
font-size: 12px;
font-weight: bold;
@@ -575,7 +575,7 @@ function initTrendChart() {
background-color: #52c41a;
border-radius: 4px;
&::before {
content: '↺';
content: '鈫?;
color: #fff;
font-size: 16px;
display: flex; justify-content: center; align-items: center; height: 100%;
@@ -586,7 +586,7 @@ function initTrendChart() {
background-color: #eb2f96;
border-radius: 50%;
&::after {
content: '';
content: '鈭?;
color: #fff;
font-size: 18px;
display: flex; justify-content: center; align-items: center; height: 100%;
@@ -602,3 +602,4 @@ function initTrendChart() {
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
</style>

View File

@@ -502,7 +502,7 @@ right: 0;
top: 0;
bottom: 0;
width: 50%;
background-color: #fff;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
display: flex;
@@ -659,3 +659,4 @@ border: none;
.flex-2 { flex: 2; }
.flex-3 { flex: 3; }
</style>

View File

@@ -430,7 +430,7 @@ right: 0;
top: 0;
bottom: 0;
width: 50%;
background-color: #fff;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
display: flex;
@@ -502,3 +502,4 @@ margin-left: 12px;
.flex-2 { flex: 2; }
.flex-3 { flex: 3; }
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">商品管理</text>
<text class="page-title">鍟嗗搧绠$悊</text>
<text class="page-subtitle">Component: ProductList</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 商品管理 的具体功能
// TODO: 瀹炵幇 鍟嗗搧绠$悊 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -264,7 +264,7 @@ right: 0;
top: 0;
bottom: 0;
width: 50%;
background-color: #fff;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
display: flex;
@@ -332,3 +332,4 @@ display: flex; flex-direction: row; justify-content: flex-end;
.flex-3 { flex: 3; }
.flex-5 { flex: 5; }
</style>

View File

@@ -1,14 +1,14 @@
<template>
<template>
<view class="product-edit-page">
<view class="page-header">
<view class="back-link" @click="goBack">
<text class="arrow">{"<"}</text>
<text class="back-txt">返回</text>
<text class="back-txt">杩斿洖</text>
</view>
<text class="header-title">编辑商品</text>
<text class="header-title">缂栬緫鍟嗗搧</text>
</view>
<!-- 步骤层 -->
<!-- 姝ラ灞?-->
<view class="steps-card">
<view class="step-items">
<view v-for="(step, index) in steps" :key="index" class="step-item" :class="{ active: activeStep === index }">
@@ -18,17 +18,17 @@
</view>
</view>
<!-- 表单内容 -->
<!-- 琛ㄥ崟鍐呭 -->
<view class="form-card">
<view class="form-item">
<view class="label"><text class="required">*</text><text>商品类型:</text></view>
<view class="label"><text class="required">*</text><text>鍟嗗搧绫诲瀷锛?/text></view>
<view class="input-wrap">
<view class="radio-group">
<view class="radio-item active">
<text class="radio-circle on"></text>
<view class="radio-txt">
<text class="main">普通商品</text>
<text class="sub">(物流发货)</text>
<text class="main">鏅€氬晢鍝?/text>
<text class="sub">(鐗╂祦鍙戣揣)</text>
</view>
</view>
</view>
@@ -36,82 +36,82 @@
</view>
<view class="form-item">
<view class="label"><text class="required">*</text><text>商品名称:</text></view>
<view class="label"><text class="required">*</text><text>鍟嗗搧鍚嶇О锛?/text></view>
<view class="input-wrap">
<view class="input-box">
<input class="real-input" value="UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060" />
<input class="real-input" value="UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060" />
<text class="count">36/80</text>
</view>
</view>
</view>
<view class="form-item">
<view class="label"><text class="required">*</text><text>单位:</text></view>
<view class="label"><text class="required">*</text><text>鍗曚綅锛?/text></view>
<view class="input-wrap">
<view class="input-box small">
<input class="real-input" value="件" />
<input class="real-input" value="浠? />
<text class="count">1/5</text>
</view>
</view>
</view>
<view class="form-item">
<view class="label"><text class="required">*</text><text>商品轮播图:</text></view>
<view class="label"><text class="required">*</text><text>鍟嗗搧杞挱鍥撅細</text></view>
<view class="input-wrap">
<view class="image-uploader">
<view v-for="(img, i) in carouselImages" :key="i" class="img-item">
<image :src="img" mode="aspectFill" />
<view class="img-close">×</view>
<view class="img-close"></view>
</view>
<view class="upload-btn">
<text class="icon">+</text>
</view>
</view>
<text class="tip">建议尺寸800*800可拖拽改变图片顺序默认首张图为主图最多上传10张</text>
<text class="tip">寤鸿灏哄锛?00*800锛屽彲鎷栨嫿鏀瑰彉鍥剧墖椤哄簭锛岄粯璁ら寮犲浘涓轰富鍥撅紝鏈€澶氫笂浼?0寮?/text>
</view>
</view>
<view class="form-item">
<view class="label"><text>添加视频:</text></view>
<view class="label"><text>娣诲姞瑙嗛锛?/text></view>
<view class="input-wrap">
<view class="upload-btn v-btn">
<text class="v-icon">📹</text>
<text class="v-icon">馃摴</text>
</view>
<text class="tip">建议时长9~30秒视频宽高比16:9</text>
<text class="tip">寤鸿鏃堕暱锛?~30绉掞紝瑙嗛瀹介珮姣?6:9</text>
</view>
</view>
<view class="form-item">
<view class="label"><text class="required">*</text><text>商品分类:</text></view>
<view class="label"><text class="required">*</text><text>鍟嗗搧鍒嗙被锛?/text></view>
<view class="input-wrap">
<view class="tag-selector">
<view v-for="tag in categories" :key="tag" class="tag-item">
<text>{{ tag }}</text>
<text class="close">×</text>
<text class="close"></text>
</view>
<text class="add-link">新增分类</text>
<text class="add-link">鏂板鍒嗙被</text>
</view>
</view>
</view>
<view class="form-item">
<view class="label"><text>商品标签:</text></view>
<view class="label"><text>鍟嗗搧鏍囩锛?/text></view>
<view class="input-wrap">
<view class="mock-btn-select">选择标签</view>
<view class="mock-btn-select">閫夋嫨鏍囩</view>
</view>
</view>
<view class="form-item">
<view class="label"><text>商品状态:</text></view>
<view class="label"><text>鍟嗗搧鐘舵€侊細</text></view>
<view class="input-wrap">
<view class="radio-group-simple">
<view class="radio-simple on">
<text class="dot"></text>
<text>上架</text>
<text>涓婃灦</text>
</view>
<view class="radio-simple">
<text class="dot"></text>
<text>下架</text>
<text>涓嬫灦</text>
</view>
</view>
</view>
@@ -119,8 +119,8 @@
</view>
<view class="footer-btns">
<button class="btn-next">下一步</button>
<button class="btn-save">保存</button>
<button class="btn-next">涓嬩竴姝?/button>
<button class="btn-save">淇濆瓨</button>
</view>
</view>
</template>
@@ -130,7 +130,7 @@ import { ref } from 'vue'
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
const activeStep = ref(0)
const steps = ['基础信息', '规格库存', '商品详情', '物流设置', '会员价/佣金', '营销设置', '其他设置']
const steps = ['鍩虹淇℃伅', '瑙勬牸搴撳瓨', '鍟嗗搧璇︽儏', '鐗╂祦璁剧疆', '浼氬憳浠?浣i噾', '钀ラ攢璁剧疆', '鍏朵粬璁剧疆']
const carouselImages = ref([
'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
@@ -138,7 +138,7 @@ const carouselImages = ref([
'https://img2.baidu.com/it/u=3775079632,546700868&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500'
])
const categories = ref(['生活家居', '运动专区 / 361', '运动专区 / 特步', '运动专区 / 匹克'])
const categories = ref(['鐢熸椿瀹跺眳', '杩愬姩涓撳尯 / 361', '杩愬姩涓撳尯 / 鐗规', '杩愬姩涓撳尯 / 鍖瑰厠'])
function goBack() {
openRoute('product_productList')
@@ -147,9 +147,9 @@ function goBack() {
<style scoped lang="scss">
.product-edit-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* padding removed */
}
.page-header {
@@ -249,7 +249,7 @@ function goBack() {
.sub { font-size: 12px; color: #999; }
}
&::after {
content: '✓';
content: '鉁?;
position: absolute;
right: 0; bottom: 0;
background: #1890ff; color: #fff; font-size: 10px; padding: 0 2px;
@@ -345,3 +345,4 @@ function goBack() {
.btn-save { background: #fff; color: #1890ff; border: 1px solid #1890ff; padding: 0 24px; height: 40px; border-radius: 4px; }
}
</style>

View File

@@ -1,47 +1,47 @@
<template>
<template>
<view class="product-list-page">
<!-- 1. 搜索表单 -->
<!-- 1. 鎼滅储琛ㄥ崟 -->
<view class="search-card">
<view class="search-row">
<view class="search-item">
<text class="label">商品搜索:</text>
<input class="mock-input" placeholder="请输入商品名称/关键字/ID" />
<text class="label">鍟嗗搧鎼滅储锛?/text>
<input class="mock-input" placeholder="璇疯緭鍏ュ晢鍝佸悕绉?鍏抽敭瀛?ID" />
</view>
<view class="search-item">
<text class="label">商品类型:</text>
<text class="label">鍟嗗搧绫诲瀷锛?/text>
<view class="mock-select">
<text>全部</text>
<text class="arrow">▼</text>
<text>鍏ㄩ儴</text>
<text class="arrow">鈻?/text>
</view>
</view>
<view class="search-item">
<text class="label">商品分类:</text>
<text class="label">鍟嗗搧鍒嗙被锛?/text>
<view class="mock-select">
<text>请选择</text>
<text class="arrow">▼</text>
<text>璇烽€夋嫨</text>
<text class="arrow">鈻?/text>
</view>
</view>
<view class="search-btns">
<button class="btn-primary">查询</button>
<button class="btn-reset">重置</button>
<button class="btn-primary">鏌ヨ</button>
<button class="btn-reset">閲嶇疆</button>
<view class="expand-control">
<text class="expand-txt">展开</text>
<text class="expand-arrow">▼</text>
<text class="expand-txt">灞曞紑</text>
<text class="expand-arrow">鈻?/text>
</view>
</view>
</view>
<view class="search-row mt-12">
<view class="search-item">
<text class="label">配送方式:</text>
<text class="label">閰嶉€佹柟寮忥細</text>
<view class="mock-select">
<text>全部</text>
<text class="arrow">▼</text>
<text>鍏ㄩ儴</text>
<text class="arrow">鈻?/text>
</view>
</view>
</view>
</view>
<!-- 2. 商品状态 Tabs -->
<!-- 2. 鍟嗗搧鐘舵€?Tabs -->
<view class="status-tabs-wrap">
<view class="status-tabs">
<view
@@ -56,46 +56,46 @@
</view>
</view>
<!-- 3. 操作按钮行 -->
<!-- 3. 鎿嶄綔鎸夐挳琛?-->
<view class="action-bar">
<view class="left-actions">
<button class="btn-add" @click="goEdit(null)">添加商品</button>
<button class="btn-collect">商品采集</button>
<button class="btn-add" @click="goEdit(null)">娣诲姞鍟嗗搧</button>
<button class="btn-collect">鍟嗗搧閲囬泦</button>
<view class="btn-dropdown">
<text>批量修改</text>
<text class="arrow">▼</text>
<text>鎵归噺淇敼</text>
<text class="arrow">鈻?/text>
</view>
<view class="btn-dropdown">
<text>商品迁移</text>
<text class="arrow">▼</text>
<text>鍟嗗搧杩佺Щ</text>
<text class="arrow">鈻?/text>
</view>
<button class="btn-export">数据导出</button>
<button class="btn-export">鏁版嵁瀵煎嚭</button>
</view>
</view>
<!-- 4. 商品列表表格 -->
<!-- 4. 鍟嗗搧鍒楄〃琛ㄦ牸 -->
<view class="list-card">
<view class="table-v5">
<view class="th-row">
<view class="th col-check"><text>□</text></view>
<view class="th col-id"><text>商品ID</text></view>
<view class="th col-img"><text>商品图</text></view>
<view class="th col-name"><text>商品名称</text></view>
<view class="th col-activity"><text>参与活动</text></view>
<view class="th col-type"><text>商品类型</text></view>
<view class="th col-price"><text>商品售价</text></view>
<view class="th col-sales"><text>销量</text></view>
<view class="th col-stock"><text>库存</text></view>
<view class="th col-sort"><text>排序</text></view>
<view class="th col-status"><text>状态</text></view>
<view class="th col-op"><text>操作</text></view>
<view class="th col-check"><text>鈻?/text></view>
<view class="th col-id"><text>鍟嗗搧ID</text></view>
<view class="th col-img"><text>鍟嗗搧鍥?/text></view>
<view class="th col-name"><text>鍟嗗搧鍚嶇О</text></view>
<view class="th col-activity"><text>鍙備笌娲诲姩</text></view>
<view class="th col-type"><text>鍟嗗搧绫诲瀷</text></view>
<view class="th col-price"><text>鍟嗗搧鍞环</text></view>
<view class="th col-sales"><text>閿€閲?/text></view>
<view class="th col-stock"><text>搴撳瓨</text></view>
<view class="th col-sort"><text>鎺掑簭</text></view>
<view class="th col-status"><text>鐘舵€?/text></view>
<view class="th col-op"><text>鎿嶄綔</text></view>
</view>
<view v-for="(item, index) in productList" :key="index"
class="tr-row"
:style="{ zIndex: activeDropdownId === item.id ? 1000 : 1 }"
>
<view class="td col-check"><text>□</text></view>
<view class="td col-check"><text>鈻?/text></view>
<view class="td col-id"><text>{{ item.id }}</text></view>
<view class="td col-img">
<image class="p-img" :src="item.image" mode="aspectFill" />
@@ -116,28 +116,28 @@
<view class="td col-sort"><text>{{ item.sort }}</text></view>
<view class="td col-status">
<view class="mock-switch" :class="{ on: item.status === 1 }">
<text class="switch-txt">{{ item.status === 1 ? '上架' : '下架' }}</text>
<text class="switch-txt">{{ item.status === 1 ? '涓婃灦' : '涓嬫灦' }}</text>
<view class="switch-dot"></view>
</view>
</view>
<view class="td col-op op-group">
<text class="op-link" @click.stop="goEdit(item.id)">编辑</text>
<text class="op-link" @click.stop="goEdit(item.id)">缂栬緫</text>
<text class="op-divider">|</text>
<view class="more-hover-box"
@mouseenter="activeDropdownId = item.id"
@mouseleave="activeDropdownId = null"
>
<view class="more-trigger-txt">
<text class="more-link-text">更多</text>
<text class="more-arrow-icon"></text>
<text class="more-link-text">鏇村</text>
<text class="more-arrow-icon">鈭?/text>
</view>
<view class="dropdown-list-container" v-show="activeDropdownId === item.id">
<view class="dropdown-triangle"></view>
<view class="dropdown-menu-body">
<text class="menu-item" @click.stop="goReviews(item.id)">查看评论</text>
<text class="menu-item" @click.stop="goMemberPrice(item.id)">会员价管理</text>
<text class="menu-item" @click.stop="uni.showToast({title:'佣金管理开发中', icon:'none'})">佣金管理</text>
<text class="menu-item danger-item" @click.stop="moveToRecycle(item.id)">{{ activeStatus === 'recycle' ? '恢复商品' : '移到回收站' }}</text>
<text class="menu-item" @click.stop="goReviews(item.id)">鏌ョ湅璇勮</text>
<text class="menu-item" @click.stop="goMemberPrice(item.id)">浼氬憳浠风鐞?/text>
<text class="menu-item" @click.stop="uni.showToast({title:'浣i噾绠$悊寮€鍙戜腑', icon:'none'})">浣i噾绠$悊</text>
<text class="menu-item danger-item" @click.stop="moveToRecycle(item.id)">{{ activeStatus === 'recycle' ? '鎭㈠鍟嗗搧' : '绉诲埌鍥炴敹绔? }}</text>
</view>
</view>
</view>
@@ -145,9 +145,9 @@
</view>
</view>
<!-- 5. 分页 -->
<!-- 5. 鍒嗛〉 -->
<view class="pagination-row">
<text class="total">{{ total }} 条</text>
<text class="total">鍏?{{ total }} 鏉?/text>
<view class="page-ctrl">
<text class="page-btn disabled">{"<"}</text>
<text class="page-num active">1</text>
@@ -168,20 +168,20 @@ const activeStatus = ref('selling')
const activeDropdownId = ref<number | null>(null)
const statusTabs = ref([
{ key: 'selling', label: '出售中的商品', count: 49 },
{ key: 'warehouse', label: '仓库中的商品', count: 4 },
{ key: 'soldout', label: '已经售罄商品', count: 11 },
{ key: 'alarm', label: '警戒库存商品', count: 27 },
{ key: 'recycle', label: '回收站的商品', count: 176 },
{ key: 'selling', label: '鍑哄敭涓殑鍟嗗搧', count: 49 },
{ key: 'warehouse', label: '浠撳簱涓殑鍟嗗搧', count: 4 },
{ key: 'soldout', label: '宸茬粡鍞絼鍟嗗搧', count: 11 },
{ key: 'alarm', label: '璀︽垝搴撳瓨鍟嗗搧', count: 27 },
{ key: 'recycle', label: '鍥炴敹绔欑殑鍟嗗搧', count: 176 },
])
const productList = ref([
{
id: 963,
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
name: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060',
activities: ['kj', 'pt'],
typeName: '普通商品',
typeName: '鏅€氬晢鍝?,
price: '0.01',
sales: 639,
stock: 1602,
@@ -191,9 +191,9 @@ const productList = ref([
{
id: 108,
image: 'https://img2.baidu.com/it/u=3033501986,2204481084&fm=253&fmt=auto&app=138&f=JPEG?w=569&h=500',
name: 'FOMIX 蛋壳椅 进口头层牛皮橙色单人沙发椅Egg chair设计师师单椅单沙头层牛皮/单椅',
name: 'FOMIX 铔嬪3妞?杩涘彛澶村眰鐗涚毊姗欒壊鍗曚汉娌欏彂妞匛gg chair璁捐甯堝笀鍗曟鍗曟矙澶村眰鐗涚毊/鍗曟',
activities: ['pt', 'ms'],
typeName: '普通商品',
typeName: '鏅€氬晢鍝?,
price: '7580.00',
sales: 14,
stock: 16638,
@@ -203,9 +203,9 @@ const productList = ref([
{
id: 48,
image: 'https://img0.baidu.com/it/u=1762118431,3101886131&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水蓝/传奇墨水蓝/白 XL',
name: '闃胯开杈炬柉瀹樼綉 adidas BBALL CAP COT 鐢峰コ璁粌杩愬姩甯藉瓙FQ5270 浼犲澧ㄦ按钃?浼犲澧ㄦ按钃?鐧?XL',
activities: ['kj', 'pt', 'ms'],
typeName: '普通商品',
typeName: '鏅€氬晢鍝?,
price: '100.00',
sales: 841,
stock: 2318,
@@ -215,9 +215,9 @@ const productList = ref([
])
function getActivityName(tag: string): string {
if (tag === 'kj') return '砍价'
if (tag === 'pt') return '拼团'
if (tag === 'ms') return '秒杀'
if (tag === 'kj') return '鐮嶄环'
if (tag === 'pt') return '鎷煎洟'
if (tag === 'ms') return '绉掓潃'
return tag
}
@@ -234,13 +234,13 @@ function goMemberPrice(id: number) {
}
function moveToRecycle(id: number) {
const action = activeStatus.value === 'recycle' ? '恢复' : '移到回收站';
const action = activeStatus.value === 'recycle' ? '鎭㈠' : '绉诲埌鍥炴敹绔?;
uni.showModal({
title: '提示',
content: `确认要将该商品${action}吗?`,
title: '鎻愮ず',
content: `纭瑕佸皢璇ュ晢鍝?{action}鍚楋紵`,
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '操作成功', icon: 'success' });
uni.showToast({ title: '鎿嶄綔鎴愬姛', icon: 'success' });
}
}
})
@@ -249,9 +249,9 @@ function moveToRecycle(id: number) {
<style scoped lang="scss">
.product-list-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* padding removed */
}
.search-card {
@@ -457,7 +457,7 @@ function moveToRecycle(id: number) {
display: flex;
flex-direction: row;
align-items: center;
pointer-events: none; /* 让事件透传给 more-hover-box */
pointer-events: none; /* 璁╀簨浠堕€忎紶缁?more-hover-box */
}
.more-link-text {
@@ -476,7 +476,7 @@ function moveToRecycle(id: number) {
top: 35px;
right: -10px;
width: 120px;
padding-top: 8px; /* 增加感应连续性 */
padding-top: 8px; /* 澧炲姞鎰熷簲杩炵画鎬?*/
z-index: 9999;
}
@@ -614,3 +614,4 @@ function moveToRecycle(id: number) {
}
}
</style>

View File

@@ -1,30 +1,30 @@
<template>
<template>
<view class="member-price-page">
<view class="page-header">
<view class="back-link" @click="goBack">
<text class="arrow">{"<"}</text>
<text class="back-txt">返回</text>
<text class="back-txt">杩斿洖</text>
</view>
<text class="header-title">会员价管理</text>
<text class="header-title">浼氬憳浠风鐞?/text>
</view>
<view class="content-card">
<view class="product-info">
<image class="p-img" src="https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500" mode="aspectFill" />
<text class="p-name">UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫</text>
<text class="p-name">UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よ</text>
</view>
<view class="price-table">
<view class="th-row">
<view class="th flex-2"><text>规格名称</text></view>
<view class="th flex-1"><text>售价</text></view>
<view class="th flex-1"><text>普通会员价</text></view>
<view class="th flex-1"><text>黄金会员价</text></view>
<view class="th flex-1"><text>铂金会员价</text></view>
<view class="th flex-2"><text>瑙勬牸鍚嶇О</text></view>
<view class="th flex-1"><text>鍞环</text></view>
<view class="th flex-1"><text>鏅€氫細鍛樹环</text></view>
<view class="th flex-1"><text>榛勯噾浼氬憳浠?/text></view>
<view class="th flex-1"><text>閾傞噾浼氬憳浠?/text></view>
</view>
<view class="tr-row">
<view class="td flex-2"><text>XL,卡其</text></view>
<view class="td flex-1"><text>¥99.00</text></view>
<view class="td flex-2"><text>XL,鍗″叾</text></view>
<view class="td flex-1"><text>锟?9.00</text></view>
<view class="td flex-1"><input class="price-input" value="89.00" /></view>
<view class="td flex-1"><input class="price-input" value="79.00" /></view>
<view class="td flex-1"><input class="price-input" value="69.00" /></view>
@@ -32,7 +32,7 @@
</view>
<view class="footer-btns">
<button class="btn-save">保存设置</button>
<button class="btn-save">淇濆瓨璁剧疆</button>
</view>
</view>
</view>
@@ -47,7 +47,7 @@ function goBack() {
</script>
<style scoped lang="scss">
.member-price-page { padding: 20px; background: #f5f7f9; min-height: 100vh; }
.member-price-page { /* padding removed */ background: #f5f7f9; }
.page-header { display: flex; flex-direction: row; align-items: center; gap: 16px; margin-bottom: 20px;
.back-link { display: flex; flex-direction: row; align-items: center; gap: 4px; color: #666; cursor: pointer; }
.header-title { font-size: 16px; font-weight: bold; color: #333; }
@@ -66,3 +66,4 @@ function goBack() {
.price-input { border: 1px solid #dcdfe6; border-radius: 4px; height: 32px; padding: 0 8px; text-align: center; width: 80%; font-size: 13px; }
.footer-btns { margin-top: 40px; display: flex; justify-content: center; .btn-save { background: #1890ff; color: #fff; border: none; padding: 0 32px; height: 40px; border-radius: 4px; } }
</style>

View File

@@ -1,22 +1,22 @@
<template>
<template>
<view class="product-statistic-page">
<!-- 商品概况头部 -->
<!-- 鍟嗗搧姒傚喌澶撮儴 -->
<view class="page-header-row">
<view class="title-wrap">
<text class="page-title">商品概况</text>
<text class="page-title">鍟嗗搧姒傚喌</text>
<view class="info-icon">?</view>
</view>
<view class="header-right">
<view class="date-picker-wrap">
<text class="calendar-emoji">📅</text>
<text class="calendar-emoji">馃搮</text>
<text class="date-range">2026/01/04 - 2026/02/02</text>
</view>
<button class="btn-query">查询</button>
<button class="btn-export">导出</button>
<button class="btn-query">鏌ヨ</button>
<button class="btn-export">瀵煎嚭</button>
</view>
</view>
<!-- 统计指标网格 (使用统一响应式网格) -->
<!-- 缁熻鎸囨爣缃戞牸 (浣跨敤缁熶竴鍝嶅簲寮忕綉鏍? -->
<view class="kpi-grid">
<view v-for="(item, index) in statItems" :key="index" class="stat-card">
<view class="stat-main">
@@ -27,11 +27,11 @@
<text class="stat-label">{{ item.label }}</text>
<text class="stat-value">{{ item.value }}</text>
<view class="stat-compare">
<text class="compare-label">坏比增长:</text>
<text class="compare-label">鍧忔瘮澧為暱锛?/text>
<text class="compare-val" :class="item.trendClass">
{{ item.compare }}
<text v-if="item.trend === 'up'" class="arrow">▲</text>
<text v-else-if="item.trend === 'down'" class="arrow">▼</text>
<text v-if="item.trend === 'up'" class="arrow">鈻?/text>
<text v-else-if="item.trend === 'down'" class="arrow">鈻?/text>
<text v-else>-</text>
</text>
</view>
@@ -40,17 +40,17 @@
</view>
</view>
<!-- 图表卡片 -->
<!-- 鍥捐〃鍗$墖 -->
<view class="chart-card">
<view class="chart-header">
<view class="legend-wrap">
<view class="legend-item"><view class="dot purple"></view><text>商品浏览量</text></view>
<view class="legend-item"><view class="dot orange"></view><text>商品访客量</text></view>
<view class="legend-item"><view class="dot blue"></view><text>支付金额</text></view>
<view class="legend-item"><view class="dot green"></view><text>退款金额</text></view>
<view class="legend-item"><view class="dot purple"></view><text>鍟嗗搧娴忚閲?/text></view>
<view class="legend-item"><view class="dot orange"></view><text>鍟嗗搧璁垮閲?/text></view>
<view class="legend-item"><view class="dot blue"></view><text>鏀粯閲戦</text></view>
<view class="legend-item"><view class="dot green"></view><text>閫€娆鹃噾棰?/text></view>
</view>
<view class="download-icon">
<text class="download-emoji">📥</text>
<text class="download-emoji">馃摜</text>
</view>
</view>
<view class="chart-main">
@@ -58,36 +58,36 @@
</view>
</view>
<!-- 商品排行 -->
<!-- 鍟嗗搧鎺掕 -->
<view class="ranking-card">
<view class="ranking-header">
<text class="ranking-title">商品排行</text>
<text class="ranking-title">鍟嗗搧鎺掕</text>
<view class="ranking-filters">
<view class="mock-select-wrap">
<text class="select-val">浏览量</text>
<text class="select-arrow">▼</text>
<text class="select-val">娴忚閲?/text>
<text class="select-arrow">鈻?/text>
</view>
<view class="date-picker-wrap">
<text class="calendar-emoji">📅</text>
<text class="calendar-emoji">馃搮</text>
<text class="date-range">2026/01/04 - 2026/02/02</text>
</view>
<button class="btn-query small">查询</button>
<button class="btn-query small">鏌ヨ</button>
</view>
</view>
<view class="ranking-table">
<view class="table-header">
<text class="th col-id">ID</text>
<text class="th col-img">商品图片</text>
<text class="th col-name">商品名称</text>
<text class="th col-num">浏览量</text>
<text class="th col-num">访客数</text>
<text class="th col-num">加购件数</text>
<text class="th col-num">下单件数</text>
<text class="th col-num">支付件数</text>
<text class="th col-num">支付金额</text>
<text class="th col-num">收藏数</text>
<text class="th col-num wide">访客-支付转化率(%)</text>
<text class="th col-img">鍟嗗搧鍥剧墖</text>
<text class="th col-name">鍟嗗搧鍚嶇О</text>
<text class="th col-num">娴忚閲?/text>
<text class="th col-num">璁垮鏁?/text>
<text class="th col-num">鍔犺喘浠舵暟</text>
<text class="th col-num">涓嬪崟浠舵暟</text>
<text class="th col-num">鏀粯浠舵暟</text>
<text class="th col-num">鏀粯閲戦</text>
<text class="th col-num">鏀惰棌鏁?/text>
<text class="th col-num wide">璁垮-鏀粯杞寲鐜?%)</text>
</view>
<view v-for="(item, index) in rankingList" :key="index" class="table-row">
<text class="td col-id">{{ item.id }}</text>
@@ -116,19 +116,19 @@ import { ref, onMounted } from 'vue'
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
const statItems = ref([
{ label: '商品浏览量', value: '7576', compare: '0.93%', trend: 'up', trendClass: 'up-red', bgColor: '#e6f7ff', emoji: '👁️' },
{ label: '商品访客量', value: '765', compare: '0.79%', trend: 'up', trendClass: 'up-red', bgColor: '#f6ffed', emoji: '👤' },
{ label: '支付件数', value: '322', compare: '-49.52%', trend: 'down', trendClass: 'down-green', bgColor: '#fff7e6', emoji: '🛍️' },
{ label: '支付金额', value: '443254.62', compare: '-63.62%', trend: 'down', trendClass: 'down-green', bgColor: '#f9f0ff', emoji: '💰' },
{ label: '退款件数', value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#e6f7ff', emoji: '🔄' },
{ label: '退款金额', value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#f6ffed', emoji: '💴' }
{ label: '鍟嗗搧娴忚閲?, value: '7576', compare: '0.93%', trend: 'up', trendClass: 'up-red', bgColor: '#e6f7ff', emoji: '馃憗锔? },
{ label: '鍟嗗搧璁垮閲?, value: '765', compare: '0.79%', trend: 'up', trendClass: 'up-red', bgColor: '#f6ffed', emoji: '馃懁' },
{ label: '鏀粯浠舵暟', value: '322', compare: '-49.52%', trend: 'down', trendClass: 'down-green', bgColor: '#fff7e6', emoji: '馃泹锔? },
{ label: '鏀粯閲戦', value: '443254.62', compare: '-63.62%', trend: 'down', trendClass: 'down-green', bgColor: '#f9f0ff', emoji: '馃挵' },
{ label: '閫€娆句欢鏁?, value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#e6f7ff', emoji: '馃攧' },
{ label: '閫€娆鹃噾棰?, value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#f6ffed', emoji: '馃挻' }
])
const rankingList = ref([
{
id: 963,
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
name: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060',
views: 1200,
visitors: 246,
cartCount: 74,
@@ -141,7 +141,7 @@ const rankingList = ref([
{
id: 116,
image: 'https://img2.baidu.com/it/u=3775079632,546700868&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室超高清手机便携投影机 (4K超清 支持侧投 手机同屏 华为一碰即投)',
name: '鐖卞鑹烘櫤鑳?濂囬亣LT01 鎶曞奖浠?瀹剁敤鍗у瓒呴珮娓呮墜鏈轰究鎼烘姇褰辨満 (4K瓒呮竻 鏀寔渚ф姇 鎵嬫満鍚屽睆 鍗庝负涓€纰板嵆鎶?',
views: 959,
visitors: 376,
cartCount: 1,
@@ -154,7 +154,7 @@ const rankingList = ref([
{
id: 48,
image: 'https://img0.baidu.com/it/u=1762118431,3101886131&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水蓝/传奇墨水蓝/白 XL',
name: '闃胯开杈炬柉瀹樼綉 adidas BBALL CAP COT 鐢峰コ璁粌杩愬姩甯藉瓙FQ5270 浼犲澧ㄦ按钃?浼犲澧ㄦ按钃?鐧?XL',
views: 758,
visitors: 207,
cartCount: 63,
@@ -167,7 +167,7 @@ const rankingList = ref([
{
id: 108,
image: 'https://img2.baidu.com/it/u=3033501986,2204481084&fm=253&fmt=auto&app=138&f=JPEG?w=569&h=500',
name: 'FOMIX 蛋壳椅 进口头层牛皮橙色单人沙发椅Egg chair设计师师单椅单沙头层牛皮/单椅',
name: 'FOMIX 铔嬪3妞?杩涘彛澶村眰鐗涚毊姗欒壊鍗曚汉娌欏彂妞匛gg chair璁捐甯堝笀鍗曟鍗曟矙澶村眰鐗涚毊/鍗曟',
views: 730,
visitors: 216,
cartCount: 26999,
@@ -251,14 +251,14 @@ function initChart() {
yAxis: [
{
type: 'value',
name: '金额',
name: '閲戦',
nameTextStyle: { color: '#8c8c8c', padding: [0, 30, 0, 0] },
splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
axisLabel: { color: '#8c8c8c' }
},
{
type: 'value',
name: '数量',
name: '鏁伴噺',
nameTextStyle: { color: '#8c8c8c', padding: [0, 0, 0, 30] },
splitLine: { show: false },
axisLabel: { color: '#8c8c8c' }
@@ -266,7 +266,7 @@ function initChart() {
],
series: [
{
name: '商品浏览量',
name: '鍟嗗搧娴忚閲?,
type: 'line',
yAxisIndex: 1,
smooth: true,
@@ -276,7 +276,7 @@ function initChart() {
data: [90, 110, 115, 100, 95, 80, 60, 40, 70, 85, 75, 65, 70, 80, 100, 120, 110, 90, 60, 95, 115, 110, 85, 50, 45, 55, 75]
},
{
name: '商品访客量',
name: '鍟嗗搧璁垮閲?,
type: 'line',
yAxisIndex: 1,
smooth: true,
@@ -286,14 +286,14 @@ function initChart() {
data: [15, 12, 10, 8, 11, 14, 13, 8, 9, 11, 10, 15, 12, 11, 9, 12, 14, 15, 11, 10, 13, 15, 11, 8, 12, 10, 14]
},
{
name: '支付金额',
name: '鏀粯閲戦',
type: 'bar',
barWidth: '25%',
itemStyle: { color: '#1890ff' },
data: [10, 5, 8, 0, 145, 15, 5, 0, 0, 0, 0, 5, 30, 0, 15, 20, 100, 20, 25, 5, 1, 3, 70, 5, 10, 5, 15, 10]
},
{
name: '退款金额',
name: '閫€娆鹃噾棰?,
type: 'bar',
barWidth: '25%',
itemStyle: { color: '#52c41a' },
@@ -315,9 +315,9 @@ function initChart() {
<style scoped lang="scss">
.product-statistic-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
.page-header-row {
@@ -367,7 +367,7 @@ function initChart() {
.btn-query { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
.btn-export { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
/* stat-grid 已废弃,由全局 kpi-grid 接管 */
/* stat-grid 宸插簾寮冿紝鐢卞叏灞€ kpi-grid 鎺ョ */
.stat-card {
background: #fff;
border-radius: 8px;
@@ -458,7 +458,7 @@ function initChart() {
}
.main-chart { width: 100%; height: 100%; }
/* 商品排行 */
/* 鍟嗗搧鎺掕 */
.ranking-card {
background: #fff;
border-radius: 8px;
@@ -538,7 +538,7 @@ function initChart() {
justify-content: center;
}
/* 列宽度配置 */
/* 鍒楀搴﹂厤缃?*/
.col-id { width: 60px; }
.col-img { width: 100px; }
.col-name { flex: 1; text-align: left; justify-content: flex-start; }
@@ -559,3 +559,4 @@ function initChart() {
}
</style>

View File

@@ -1,62 +1,62 @@
<template>
<template>
<view class="product-reply-page">
<!-- 1. 搜索筛选 -->
<!-- 1. 鎼滅储绛涢€?-->
<view class="search-card">
<view class="search-row">
<view class="search-item">
<text class="label">评价时间:</text>
<text class="label">璇勪环鏃堕棿锛?/text>
<view class="mock-date-range">
<text class="emoji">📅</text>
<text class="txt">开始日期 - 结束日期</text>
<text class="emoji">馃搮</text>
<text class="txt">寮€濮嬫棩鏈?- 缁撴潫鏃ユ湡</text>
</view>
</view>
<view class="search-item">
<text class="label">评价状态:</text>
<view class="mock-select"><text>请选择</text><text class="arrow">▼</text></view>
<text class="label">璇勪环鐘舵€侊細</text>
<view class="mock-select"><text>璇烽€夋嫨</text><text class="arrow">鈻?/text></view>
</view>
<view class="search-item">
<text class="label">审核状态:</text>
<view class="mock-select"><text>请选择</text><text class="arrow">▼</text></view>
<text class="label">瀹℃牳鐘舵€侊細</text>
<view class="mock-select"><text>璇烽€夋嫨</text><text class="arrow">鈻?/text></view>
</view>
</view>
<view class="search-row mt-16">
<view class="search-item">
<text class="label">商品信息:</text>
<input class="mock-input" placeholder="请输入商品信息" />
<text class="label">鍟嗗搧淇℃伅锛?/text>
<input class="mock-input" placeholder="璇疯緭鍏ュ晢鍝佷俊鎭? />
</view>
<view class="search-item">
<text class="label">用户名称:</text>
<input class="mock-input" placeholder="请输入" />
<text class="label">鐢ㄦ埛鍚嶇О锛?/text>
<input class="mock-input" placeholder="璇疯緭鍏? />
</view>
<button class="btn-primary">查询</button>
<button class="btn-primary">鏌ヨ</button>
</view>
</view>
<!-- 2. 操作行 -->
<!-- 2. 鎿嶄綔琛?-->
<view class="action-bar">
<button class="btn-primary">添加自评</button>
<button class="btn-white">批量审核</button>
<button class="btn-primary">娣诲姞鑷瘎</button>
<button class="btn-white">鎵归噺瀹℃牳</button>
</view>
<!-- 3. 数据表格 -->
<!-- 3. 鏁版嵁琛ㄦ牸 -->
<view class="list-card">
<view class="table-v5">
<view class="th-row">
<view class="th col-check"><text>□</text></view>
<view class="th col-id"><text>评论ID</text></view>
<view class="th col-product"><text>商品信息</text></view>
<view class="th col-spec"><text>规格</text></view>
<view class="th col-user"><text>用户名称</text></view>
<view class="th col-score"><text>评分</text></view>
<view class="th col-content"><text>评价内容</text></view>
<view class="th col-reply"><text>回复内容</text></view>
<view class="th col-status"><text>审核状态</text></view>
<view class="th col-time"><text>评价时间</text></view>
<view class="th col-op"><text>操作</text></view>
<view class="th col-check"><text>鈻?/text></view>
<view class="th col-id"><text>璇勮ID</text></view>
<view class="th col-product"><text>鍟嗗搧淇℃伅</text></view>
<view class="th col-spec"><text>瑙勬牸</text></view>
<view class="th col-user"><text>鐢ㄦ埛鍚嶇О</text></view>
<view class="th col-score"><text>璇勫垎</text></view>
<view class="th col-content"><text>璇勪环鍐呭</text></view>
<view class="th col-reply"><text>鍥炲鍐呭</text></view>
<view class="th col-status"><text>瀹℃牳鐘舵€?/text></view>
<view class="th col-time"><text>璇勪环鏃堕棿</text></view>
<view class="th col-op"><text>鎿嶄綔</text></view>
</view>
<view v-for="item in replyList" :key="item.id" class="tr-row">
<view class="td col-check"><text>□</text></view>
<view class="td col-check"><text>鈻?/text></view>
<view class="td col-id"><text>{{ item.id }}</text></view>
<view class="td col-product">
<image class="p-img" :src="item.image" mode="aspectFill" />
@@ -66,25 +66,25 @@
<view class="td col-user"><text>{{ item.username }}</text></view>
<view class="td col-score"><text>{{ item.score }}</text></view>
<view class="td col-content"><text class="blue-link">{{ item.content }}</text></view>
<view class="td col-reply"><text>{{ item.reply || '无' }}</text></view>
<view class="td col-reply"><text>{{ item.reply || '鏃? }}</text></view>
<view class="td col-status">
<text class="status-tag" :class="item.status === 1 ? 'pass' : 'wait'">
{{ item.status === 1 ? '通过' : '待审核' }}
{{ item.status === 1 ? '閫氳繃' : '寰呭鏍? }}
</text>
</view>
<view class="td col-time"><text>{{ item.time }}</text></view>
<view class="td col-op">
<text class="op-link">通过</text>
<text class="op-link">驳回</text>
<text class="op-link">回复</text>
<text class="op-link red">删除</text>
<text class="op-link">閫氳繃</text>
<text class="op-link">椹冲洖</text>
<text class="op-link">鍥炲</text>
<text class="op-link red">鍒犻櫎</text>
</view>
</view>
</view>
<!-- 分页 -->
<!-- 鍒嗛〉 -->
<view class="pagination-row">
<text class="total">{{ replyList.length }} 条</text>
<text class="total">鍏?{{ replyList.length }} 鏉?/text>
<view class="page-ctrl">
<text class="page-btn disabled">{"<"}</text>
<text class="page-num active">1</text>
@@ -102,8 +102,8 @@ const replyList = ref([
{
id: 1069,
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
spec: 'XL,卡其',
productName: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060',
spec: 'XL,鍗″叾',
username: 'demo998',
score: 3.5,
content: '22',
@@ -114,11 +114,11 @@ const replyList = ref([
{
id: 1059,
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
spec: 'XL,卡其',
username: '你好呀',
productName: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060',
spec: 'XL,鍗″叾',
username: '浣犲ソ鍛€',
score: 3.5,
content: '的',
content: '鐨?,
reply: '',
status: 0,
time: '2025-01-07 15:35:36'
@@ -126,11 +126,11 @@ const replyList = ref([
{
id: 980,
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
spec: 'XL,卡其',
productName: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よUWG440060',
spec: 'XL,鍗″叾',
username: 'wx209638',
score: 5,
content: '好',
content: '濂?,
reply: '',
status: 1,
time: '2024-09-12 14:20:12'
@@ -140,9 +140,9 @@ const replyList = ref([
<style scoped lang="scss">
.product-reply-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* padding removed */
}
.search-card {
@@ -264,3 +264,4 @@ const replyList = ref([
}
}
</style>

View File

@@ -1,21 +1,21 @@
<template>
<template>
<view class="admin-main">
<!-- 头部搜索 -->
<!-- 澶撮儴鎼滅储 -->
<view class="search-card">
<view class="search-row">
<view class="search-item">
<text class="search-label">规格搜索:</text>
<input class="search-input" placeholder="请输入规格名称" />
<text class="search-label">瑙勬牸鎼滅储:</text>
<input class="search-input" placeholder="璇疯緭鍏ヨ鏍煎悕绉? />
</view>
<button class="btn-query">查询</button>
<button class="btn-query">鏌ヨ</button>
</view>
</view>
<!-- 数据表格区域 -->
<!-- 鏁版嵁琛ㄦ牸鍖哄煙 -->
<view class="table-card">
<view class="table-toolbar">
<button class="btn-add" @click="showModal = true">添加商品规格</button>
<button class="btn-batch-del">批量删除</button>
<button class="btn-add" @click="showModal = true">娣诲姞鍟嗗搧瑙勬牸</button>
<button class="btn-batch-del">鎵归噺鍒犻櫎</button>
</view>
<view class="table-header">
@@ -23,20 +23,20 @@
<view class="checkbox-mock"></view>
</view>
<text class="th-cell flex-1">ID</text>
<text class="th-cell flex-3">规格名称</text>
<text class="th-cell flex-4">商品规格</text>
<text class="th-cell flex-4">商品属性</text>
<text class="th-cell flex-2 text-center">操作</text>
<text class="th-cell flex-3">瑙勬牸鍚嶇О</text>
<text class="th-cell flex-4">鍟嗗搧瑙勬牸</text>
<text class="th-cell flex-4">鍟嗗搧灞炴€?/text>
<text class="th-cell flex-2 text-center">鎿嶄綔</text>
</view>
<view class="table-body">
<view v-if="list.length === 0" class="empty-box">
<text class="empty-text">暂无数据</text>
<text class="empty-text">鏆傛棤鏁版嵁</text>
</view>
<view v-for="(item, index) in list" :key="index" class="table-row">
<view class="td-cell flex-1 row-center">
<view class="checkbox-mock" :class="item.selected ? 'checked' : ''" @click="item.selected = !item.selected">
<text v-if="item.selected" class="check-mark">✓</text>
<text v-if="item.selected" class="check-mark">鉁?/text>
</view>
</view>
<text class="td-cell flex-1 color-9">{{ item.id }}</text>
@@ -44,46 +44,46 @@
<text class="td-cell flex-4">{{ item.specs }}</text>
<text class="td-cell flex-4">{{ item.attrs }}</text>
<view class="td-cell flex-2 row-center">
<text class="btn-link">编辑</text>
<text class="btn-link">缂栬緫</text>
<view class="divider"></view>
<text class="btn-link delete" @click="deleteItem(index)">删除</text>
<text class="btn-link delete" @click="deleteItem(index)">鍒犻櫎</text>
</view>
</view>
</view>
</view>
<!-- 添加规格弹窗 -->
<!-- 娣诲姞瑙勬牸寮圭獥 -->
<view class="modal-mask" v-if="showModal" @click="showModal = false">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">添加商品规格</text>
<text class="modal-close" @click="showModal = false">×</text>
<text class="modal-title">娣诲姞鍟嗗搧瑙勬牸</text>
<text class="modal-close" @click="showModal = false"></text>
</view>
<view class="modal-body">
<view class="modal-form">
<view class="form-item">
<view class="form-label-box"><text class="form-label">规格名称:</text></view>
<view class="form-label-box"><text class="form-label">瑙勬牸鍚嶇О:</text></view>
<view class="form-input-box">
<input class="modal-input" v-model="form.name" placeholder="请输入规格名称" />
<input class="modal-input" v-model="form.name" placeholder="璇疯緭鍏ヨ鏍煎悕绉? />
</view>
</view>
<view class="form-item">
<view class="form-label-box"><text class="form-label">商品规格:</text></view>
<view class="form-label-box"><text class="form-label">鍟嗗搧瑙勬牸:</text></view>
<view class="form-input-box">
<input class="modal-input" v-model="form.specs" placeholder="请输入商品规格" />
<input class="modal-input" v-model="form.specs" placeholder="璇疯緭鍏ュ晢鍝佽鏍? />
</view>
</view>
<view class="form-item">
<view class="form-label-box"><text class="form-label">商品属性:</text></view>
<view class="form-label-box"><text class="form-label">鍟嗗搧灞炴€?</text></view>
<view class="form-input-box">
<input class="modal-input" v-model="form.attrs" placeholder="请输入商品属性" />
<input class="modal-input" v-model="form.attrs" placeholder="璇疯緭鍏ュ晢鍝佸睘鎬? />
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn-modal-cancel" @click="showModal = false">取消</button>
<button class="btn-modal-submit" @click="saveAttr">确定</button>
<button class="btn-modal-cancel" @click="showModal = false">鍙栨秷</button>
<button class="btn-modal-submit" @click="saveAttr">纭畾</button>
</view>
</view>
</view>
@@ -102,11 +102,11 @@ interface AttrItem {
}
const list = reactive<AttrItem[]>([
{ id: 104, name: '颜色', specs: '红色,蓝色,黑色,白色', attrs: '颜色属性', selected: false },
{ id: 105, name: '尺寸', specs: 'S,M,L,XL,XXL', attrs: '服装尺寸', selected: false },
{ id: 106, name: '材质', specs: '纯棉,涤纶,真丝', attrs: '面料材质', selected: false },
{ id: 107, name: '内存', specs: '8G,16G,32G', attrs: '硬件参数', selected: false },
{ id: 108, name: '存储', specs: '128G,256G,512G', attrs: '容量', selected: false }
{ id: 104, name: '棰滆壊', specs: '绾㈣壊,钃濊壊,榛戣壊,鐧借壊', attrs: '棰滆壊灞炴€?, selected: false },
{ id: 105, name: '灏哄', specs: 'S,M,L,XL,XXL', attrs: '鏈嶈灏哄', selected: false },
{ id: 106, name: '鏉愯川', specs: '绾,娑ょ憾,鐪熶笣', attrs: '闈㈡枡鏉愯川', selected: false },
{ id: 107, name: '鍐呭瓨', specs: '8G,16G,32G', attrs: '纭欢鍙傛暟', selected: false },
{ id: 108, name: '瀛樺偍', specs: '128G,256G,512G', attrs: '瀹归噺', selected: false }
])
const showModal = ref(false)
@@ -118,7 +118,7 @@ const form = reactive({
function saveAttr() {
if (!form.name) {
uni.showToast({ title: '请输入规格名称', icon: 'none' })
uni.showToast({ title: '璇疯緭鍏ヨ鏍煎悕绉?, icon: 'none' })
return
}
list.push({
@@ -132,13 +132,13 @@ function saveAttr() {
form.name = ''
form.specs = ''
form.attrs = ''
uni.showToast({ title: '添加成功', icon: 'success' })
uni.showToast({ title: '娣诲姞鎴愬姛', icon: 'success' })
}
function deleteItem(index: number) {
uni.showModal({
title: '提示',
content: '确定删除该规格吗?',
title: '鎻愮ず',
content: '纭畾鍒犻櫎璇ヨ鏍煎悧锛?,
success: (res) => {
if (res.confirm) {
list.splice(index, 1)
@@ -155,7 +155,7 @@ function deleteItem(index: number) {
min-height: 100vh;
}
/* 搜索卡片 */
/* 鎼滅储鍗$墖 */
.search-card {
background-color: #fff;
padding: 24px;
@@ -203,7 +203,7 @@ function deleteItem(index: number) {
margin-left: 0;
}
/* 表格区域 */
/* 琛ㄦ牸鍖哄煙 */
.table-card {
background-color: #fff;
padding: 24px;
@@ -348,7 +348,7 @@ function deleteItem(index: number) {
.modal-content {
width: 500px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
@@ -443,3 +443,4 @@ function deleteItem(index: number) {
.flex-4 { flex: 4; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">协议管理</text>
<text class="page-title">鍗忚绠$悊</text>
</view>
<view class="page-content">
<text class="placeholder-text">协议管理 页面开发中...</text>
<text class="placeholder-text">鍗忚绠$悊 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">管理员列表</text>
<text class="page-title">绠$悊鍛樺垪琛?/text>
</view>
<view class="page-content">
<text class="placeholder-text">管理员列表 页面开发中...</text>
<text class="placeholder-text">绠$悊鍛樺垪琛?椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">权限设置</text>
<text class="page-title">鏉冮檺璁剧疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">权限设置 页面开发中...</text>
<text class="placeholder-text">鏉冮檺璁剧疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">角色管理</text>
<text class="page-title">瑙掕壊绠$悊</text>
</view>
<view class="page-content">
<text class="placeholder-text">角色管理 页面开发中...</text>
<text class="placeholder-text">瑙掕壊绠$悊 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">配送员管理</text>
<text class="page-title">閰嶉€佸憳绠$悊</text>
</view>
<view class="page-content">
<text class="placeholder-text">配送员管理 页面开发中...</text>
<text class="placeholder-text">閰嶉€佸憳绠$悊 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">提货点设置</text>
<text class="page-title">鎻愯揣鐐硅缃?/text>
</view>
<view class="page-content">
<text class="placeholder-text">提货点设置 页面开发中...</text>
<text class="placeholder-text">鎻愯揣鐐硅缃?椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">运费模板</text>
<text class="page-title">杩愯垂妯℃澘</text>
</view>
<view class="page-content">
<text class="placeholder-text">运费模板 页面开发中...</text>
<text class="placeholder-text">杩愯垂妯℃澘 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">商品采集配置</text>
<text class="page-title">鍟嗗搧閲囬泦閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">商品采集配置 页面开发中...</text>
<text class="placeholder-text">鍟嗗搧閲囬泦閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">电子面单配置</text>
<text class="page-title">鐢靛瓙闈㈠崟閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">电子面单配置 页面开发中...</text>
<text class="placeholder-text">鐢靛瓙闈㈠崟閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">物流查询配置</text>
<text class="page-title">鐗╂祦鏌ヨ閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">物流查询配置 页面开发中...</text>
<text class="placeholder-text">鐗╂祦鏌ヨ閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">一号通配置</text>
<text class="page-title">涓€鍙烽€氶厤缃?/text>
</view>
<view class="page-content">
<text class="placeholder-text">一号通配置 页面开发中...</text>
<text class="placeholder-text">涓€鍙烽€氶厤缃?椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">一号通页面</text>
<text class="page-title">涓€鍙烽€氶〉闈?/text>
</view>
<view class="page-content">
<text class="placeholder-text">一号通页面 页面开发中...</text>
<text class="placeholder-text">涓€鍙烽€氶〉闈?椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">商城支付配置</text>
<text class="page-title">鍟嗗煄鏀粯閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">商城支付配置 页面开发中...</text>
<text class="placeholder-text">鍟嗗煄鏀粯閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">短信接口配置</text>
<text class="page-title">鐭俊鎺ュ彛閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">短信接口配置 页面开发中...</text>
<text class="placeholder-text">鐭俊鎺ュ彛閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">系统存储配置</text>
<text class="page-title">绯荤粺瀛樺偍閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">系统存储配置 页面开发中...</text>
<text class="placeholder-text">绯荤粺瀛樺偍閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">消息管理</text>
<text class="page-title">娑堟伅绠$悊</text>
</view>
<view class="page-content">
<text class="placeholder-text">消息管理 页面开发中...</text>
<text class="placeholder-text">娑堟伅绠$悊 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">管理员管理</text>
<text class="page-title">绠$悊鍛樼鐞?/text>
<text class="page-subtitle">Component: SettingSystemAdmin</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 管理员管理 的具体功能
// TODO: 瀹炵幇 绠$悊鍛樼鐞?鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">协议设置</text>
<text class="page-title">鍗忚璁剧疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">协议设置 页面开发中...</text>
<text class="placeholder-text">鍗忚璁剧疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">消息管理</text>
<text class="page-title">娑堟伅绠$悊</text>
</view>
<view class="page-content">
<text class="placeholder-text">消息管理 页面开发中...</text>
<text class="placeholder-text">娑堟伅绠$悊 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">角色管理</text>
<text class="page-title">瑙掕壊绠$悊</text>
<text class="page-subtitle">Component: SettingSystemRole</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 角色管理 的具体功能
// TODO: 瀹炵幇 瑙掕壊绠$悊 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">小票配置</text>
<text class="page-title">灏忕エ閰嶇疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">小票配置 页面开发中...</text>
<text class="placeholder-text">灏忕エ閰嶇疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,11 +1,11 @@
<template>
<template>
<view class="admin-page-container">
<view class="page-card">
<view class="page-header">
<text class="page-title">客服设置</text>
<text class="page-title">瀹㈡湇璁剧疆</text>
</view>
<view class="page-content">
<text class="placeholder-text">客服设置 页面开发中...</text>
<text class="placeholder-text">瀹㈡湇璁剧疆 椤甸潰寮€鍙戜腑...</text>
</view>
</view>
</view>
@@ -15,9 +15,10 @@
</script>
<style scoped>
.admin-page-container { padding: 20px; background-color: #f5f7f9; min-height: 100vh; }
.admin-page-container { /* padding removed */ }
.page-card { background-color: #fff; border-radius: 4px; padding: 20px; box-shadow: 0 1px 4px rgba(0,21,41,0.08); }
.page-header { margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; padding-bottom: 15px; }
.page-title { font-size: 16px; font-weight: bold; color: #303133; }
.placeholder-text { font-size: 14px; color: #909399; }
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">数据概览</text>
<text class="page-title">鏁版嵁姒傝</text>
<text class="page-subtitle">Component: StatisticIndex</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 数据概览 的具体功能
// TODO: 瀹炵幇 鏁版嵁姒傝 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,76 +1,76 @@
<template>
<template>
<view class="user-config-page">
<view class="config-card">
<!-- 顶部 Tab 切换 -->
<!-- 椤堕儴 Tab 鍒囨崲 -->
<view class="config-tabs">
<view
class="tab-item"
:class="{ active: activeTab === 0 }"
@click="activeTab = 0"
>
<text>用户等级配置</text>
<text>鐢ㄦ埛绛夌骇閰嶇疆</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 1 }"
@click="activeTab = 1"
>
<text>新用户设置</text>
<text>鏂扮敤鎴疯缃?/text>
</view>
</view>
<view class="config-content">
<!-- 用户等级配置表单 -->
<!-- 鐢ㄦ埛绛夌骇閰嶇疆琛ㄥ崟 -->
<view v-if="activeTab === 0" class="config-form">
<view class="form-item">
<view class="item-label">
<text class="label-text">用户等级启用:</text>
<text class="label-text">鐢ㄦ埛绛夌骇鍚敤锛?/text>
</view>
<view class="item-content">
<radio-group class="radio-group" @change="onLevelEnabledChange">
<label class="radio-label">
<radio value="1" :checked="levelConfig.enabled === '1'" color="#2f54eb" />
<text class="radio-text">开启</text>
<text class="radio-text">寮€鍚?/text>
</label>
<label class="radio-label">
<radio value="0" :checked="levelConfig.enabled === '0'" color="#2f54eb" />
<text class="radio-text">关闭</text>
<text class="radio-text">鍏抽棴</text>
</label>
</radio-group>
<text class="item-desc">商城用户等级功能开启关闭</text>
<text class="item-desc">鍟嗗煄鐢ㄦ埛绛夌骇鍔熻兘寮€鍚叧闂?/text>
</view>
</view>
<view class="form-item">
<view class="item-label">
<text class="label-text">订单赠送经验:</text>
<text class="label-text">璁㈠崟璧犻€佺粡楠岋細</text>
</view>
<view class="item-content">
<input class="config-input" v-model="levelConfig.orderExp" type="number" />
<text class="item-desc">下单赠送用户经验比例 (实际支付1元赠送多少经验)</text>
<text class="item-desc">涓嬪崟璧犻€佺敤鎴风粡楠屾瘮渚?(瀹為檯鏀粯1鍏冭禒閫佸灏戠粡楠?</text>
</view>
</view>
<view class="form-item">
<view class="item-label">
<text class="label-text">邀新赠送经验:</text>
<text class="label-text">閭€鏂拌禒閫佺粡楠岋細</text>
</view>
<view class="item-content">
<input class="config-input" v-model="levelConfig.inviteExp" type="number" />
<text class="item-desc">邀请一个新用户赠送用户经验值</text>
<text class="item-desc">閭€璇蜂竴涓柊鐢ㄦ埛璧犻€佺敤鎴风粡楠屽€?/text>
</view>
</view>
<view class="form-actions">
<button class="submit-btn" type="primary" @click="onSubmit">提交</button>
<button class="submit-btn" type="primary" @click="onSubmit">鎻愪氦</button>
</view>
</view>
<!-- 新用户设置表单 -->
<!-- 鏂扮敤鎴疯缃〃鍗?-->
<view v-else class="config-form">
<view class="form-item">
<view class="item-label">
<text class="label-text">用户默认头像:</text>
<text class="label-text">鐢ㄦ埛榛樿澶村儚锛?/text>
</view>
<view class="item-content">
<view class="avatar-upload">
@@ -79,51 +79,51 @@
<text class="upload-icon">+</text>
</view>
</view>
<text class="item-desc">内用户默认头像,后台添加用户以及用户登录的默认头像显示,尺寸(80*80)</text>
<text class="item-desc">鍐呯敤鎴烽粯璁ゅご鍍忥紝鍚庡彴娣诲姞鐢ㄦ埛浠ュ強鐢ㄦ埛鐧诲綍鐨勯粯璁ゅご鍍忔樉绀猴紝灏哄(80*80)</text>
</view>
</view>
<view class="form-item">
<view class="item-label">
<text class="label-text">强制手机号登录:</text>
<text class="label-text">寮哄埗鎵嬫満鍙风櫥褰曪細</text>
</view>
<view class="item-content">
<radio-group class="radio-group" @change="onForcePhoneChange">
<label class="radio-label">
<radio value="1" :checked="newUserConfig.forcePhone === '1'" color="#2f54eb" />
<text class="radio-text">强制</text>
<text class="radio-text">寮哄埗</text>
</label>
<label class="radio-label">
<radio value="0" :checked="newUserConfig.forcePhone === '0'" color="#2f54eb" />
<text class="radio-text">不强制</text>
<text class="radio-text">涓嶅己鍒?/text>
</label>
</radio-group>
<text class="item-desc">用户在授权之后强制绑定手机号,可以实现用户多端统一</text>
<text class="item-desc">鐢ㄦ埛鍦ㄦ巿鏉冧箣鍚庡己鍒剁粦瀹氭墜鏈哄彿锛屽彲浠ュ疄鐜扮敤鎴峰绔粺涓€</text>
</view>
</view>
<view class="form-item">
<view class="item-label">
<text class="label-text">赠送余额(元)</text>
<text class="label-text">璧犻€佷綑棰?鍏?锛?/text>
</view>
<view class="item-content">
<input class="config-input" v-model="newUserConfig.giftBalance" type="number" />
<text class="item-desc">新用户奖励金额必须大于等于00为不赠送</text>
<text class="item-desc">鏂扮敤鎴峰鍔遍噾棰濓紝蹇呴』澶т簬绛変簬0锛?涓轰笉璧犻€?/text>
</view>
</view>
<view class="form-item">
<view class="item-label">
<text class="label-text">赠送积分:</text>
<text class="label-text">璧犻€佺Н鍒嗭細</text>
</view>
<view class="item-content">
<input class="config-input" v-model="newUserConfig.giftIntegral" type="number" />
<text class="item-desc">新用户奖励积分必须大于等于00为不赠送</text>
<text class="item-desc">鏂扮敤鎴峰鍔辩Н鍒嗭紝蹇呴』澶т簬绛変簬0锛?涓轰笉璧犻€?/text>
</view>
</view>
<view class="form-actions">
<button class="submit-btn" type="primary" @click="onSubmit">提交</button>
<button class="submit-btn" type="primary" @click="onSubmit">鎻愪氦</button>
</view>
</view>
</view>
@@ -157,11 +157,11 @@ function onForcePhoneChange(e: any) {
}
function onSubmit() {
uni.showLoading({ title: '提交中...' })
uni.showLoading({ title: '鎻愪氦涓?..' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '修改成功',
title: '淇敼鎴愬姛',
icon: 'success'
})
}, 800)
@@ -170,9 +170,9 @@ function onSubmit() {
<style scoped lang="scss">
.user-config-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
.config-card {
@@ -339,3 +339,4 @@ function onSubmit() {
margin: 0;
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">卡密会员</text>
<text class="page-title">鍗″瘑浼氬憳</text>
<text class="page-subtitle">Component: UserGradeCard</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 卡密会员 的具体功能
// TODO: 瀹炵幇 鍗″瘑浼氬憳 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">会员记录</text>
<text class="page-title">浼氬憳璁板綍</text>
<text class="page-subtitle">Component: UserGradeRecord</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 会员记录 的具体功能
// TODO: 瀹炵幇 浼氬憳璁板綍 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">会员权益</text>
<text class="page-title">浼氬憳鏉冪泭</text>
<text class="page-subtitle">Component: UserGradeRight</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 会员权益 的具体功能
// TODO: 瀹炵幇 浼氬憳鏉冪泭 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">会员类型</text>
<text class="page-title">浼氬憳绫诲瀷</text>
<text class="page-subtitle">Component: UserGradeType</text>
</view>
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">页面占位</text>
<text class="placeholder-desc">该功能模块正在开发中</text>
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
<text class="placeholder-title">椤甸潰鍗犱綅</text>
<text class="placeholder-desc">璇ュ姛鑳芥ā鍧楁鍦ㄥ紑鍙戜腑</text>
<text class="placeholder-info">褰撳墠閲囩敤 CRMEB 璺敱浣撶郴 1:1 鏄犲皠</text>
</view>
</view>
</view>
@@ -18,14 +18,14 @@
<script setup lang="uts">
import { ref } from 'vue'
// TODO: 实现 会员类型 的具体功能
// TODO: 瀹炵幇 浼氬憳绫诲瀷 鐨勫叿浣撳姛鑳?
const loading = ref<boolean>(false)
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
/* padding removed */
background: #f5f5f5;
}
@@ -79,3 +79,5 @@ const loading = ref<boolean>(false)
color: #1890ff;
}
</style>

View File

@@ -1,78 +1,78 @@
<template>
<template>
<view class="user-group-page">
<view class="content-card">
<!-- 操作按钮行 -->
<!-- 鎿嶄綔鎸夐挳琛?-->
<view class="action-bar">
<button class="btn primary small" @click="onAddGroup">添加分组</button>
<button class="btn primary small" @click="onAddGroup">娣诲姞鍒嗙粍</button>
</view>
<!-- 分组列表表格 -->
<!-- 鍒嗙粍鍒楄〃琛ㄦ牸 -->
<view class="table-container">
<!-- 表头 -->
<!-- 琛ㄥご -->
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
<view class="col col-name"><text>分组</text></view>
<view class="col col-ops"><text>操作</text></view>
<view class="col col-name"><text>鍒嗙粍</text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<!-- 表格内容 -->
<!-- 琛ㄦ牸鍐呭 -->
<view class="table-body">
<view v-for="group in groupList" :key="group.id" class="table-row">
<view class="col col-id"><text>{{ group.id }}</text></view>
<view class="col col-name"><text>{{ group.name }}</text></view>
<view class="col col-ops">
<text class="op-link" @click="onEditGroup(group)">修改</text>
<text class="op-link" @click="onEditGroup(group)">淇敼</text>
<view class="op-divider">|</view>
<text class="op-link" @click="onDeleteGroup(group)">删除</text>
<text class="op-link" @click="onDeleteGroup(group)">鍒犻櫎</text>
</view>
</view>
</view>
</view>
<!-- 分页区域 -->
<!-- 鍒嗛〉鍖哄煙 -->
<view class="pagination-row">
<text class="total-text">{{ groupList.length }} 条</text>
<text class="total-text">鍏?{{ groupList.length }} 鏉?/text>
<view class="page-size-selector">
<text>15条/页</text>
<text class="arrow">▼</text>
<text>15鏉?椤?/text>
<text class="arrow">鈻?/text>
</view>
<view class="page-btns">
<view class="page-btn disabled"><text></text></view>
<view class="page-btn disabled"><text>鈥?/text></view>
<view class="page-btn active"><text>1</text></view>
<view class="page-btn disabled"><text></text></view>
<view class="page-btn disabled"><text>鈥?/text></view>
</view>
<view class="page-jump">
<text>前往</text>
<text>鍓嶅線</text>
<input class="jump-input" value="1" />
<text>页</text>
<text>椤?/text>
</view>
</view>
</view>
<!-- 添加/修改分组弹窗 -->
<!-- 娣诲姞/淇敼鍒嗙粍寮圭獥 -->
<view class="modal-mask" v-if="showModal" @click="closeModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ modalTitle }}</text>
<text class="modal-close" @click="closeModal">×</text>
<text class="modal-close" @click="closeModal"></text>
</view>
<view class="modal-body">
<view class="form-item">
<view class="label-box">
<text class="required">*</text>
<text class="label">分组名称:</text>
<text class="label">鍒嗙粍鍚嶇О锛?/text>
</view>
<input
class="form-input"
v-model="formData.name"
placeholder="请输入分组名称"
placeholder="璇疯緭鍏ュ垎缁勫悕绉?
autofocus
/>
</view>
</view>
<view class="modal-footer">
<button class="btn ghost" @click="closeModal">取消</button>
<button class="btn primary" @click="submitForm">确定</button>
<button class="btn ghost" @click="closeModal">鍙栨秷</button>
<button class="btn primary" @click="submitForm">纭畾</button>
</view>
</view>
</view>
@@ -82,26 +82,26 @@
<script setup lang="uts">
import { ref, reactive, computed } from 'vue'
// 分组数据
// 鍒嗙粍鏁版嵁
const groupList = ref([
{ id: 251, name: 'A类客户' },
{ id: 252, name: 'B类客户' },
{ id: 253, name: 'C类客户' },
{ id: 254, name: 'D类客户' }
{ id: 251, name: 'A绫诲鎴? },
{ id: 252, name: 'B绫诲鎴? },
{ id: 253, name: 'C绫诲鎴? },
{ id: 254, name: 'D绫诲鎴? }
])
// 弹窗状态
// 寮圭獥鐘舵€?
const showModal = ref(false)
const isEdit = ref(false)
const modalTitle = computed(() => isEdit.value ? '修改分组' : '添加分组')
const modalTitle = computed(() => isEdit.value ? '淇敼鍒嗙粍' : '娣诲姞鍒嗙粍')
// 表单数据
// 琛ㄥ崟鏁版嵁
const formData = reactive({
id: 0,
name: ''
})
// 添加分组
// 娣诲姞鍒嗙粍
function onAddGroup() {
isEdit.value = false
formData.id = 0
@@ -109,7 +109,7 @@ function onAddGroup() {
showModal.value = true
}
// 修改分组
// 淇敼鍒嗙粍
function onEditGroup(group: any) {
isEdit.value = true
formData.id = group.id
@@ -117,47 +117,47 @@ function onEditGroup(group: any) {
showModal.value = true
}
// 删除分组
// 鍒犻櫎鍒嗙粍
function onDeleteGroup(group: any) {
uni.showModal({
title: '提示',
content: '确定要删除该分组吗?',
title: '鎻愮ず',
content: '纭畾瑕佸垹闄よ鍒嗙粍鍚楋紵',
success: (res) => {
if (res.confirm) {
groupList.value = groupList.value.filter(item => item.id !== group.id)
uni.showToast({ title: '删除成功', icon: 'success' })
uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
}
}
})
}
// 关闭弹窗
// 鍏抽棴寮圭獥
function closeModal() {
showModal.value = false
}
// 提交表单
// 鎻愪氦琛ㄥ崟
function submitForm() {
if (!formData.name) {
uni.showToast({ title: '请输入分组名称', icon: 'none' })
uni.showToast({ title: '璇疯緭鍏ュ垎缁勫悕绉?, icon: 'none' })
return
}
if (isEdit.value) {
// 模拟修改
// 妯℃嫙淇敼
const index = groupList.value.findIndex(item => item.id === formData.id)
if (index > -1) {
groupList.value[index].name = formData.name
}
uni.showToast({ title: '修改成功', icon: 'success' })
uni.showToast({ title: '淇敼鎴愬姛', icon: 'success' })
} else {
// 模拟添加
// 妯℃嫙娣诲姞
const newId = groupList.value.length > 0 ? Math.max(...groupList.value.map(g => g.id)) + 1 : 1
groupList.value.push({
id: newId,
name: formData.name
})
uni.showToast({ title: '添加成功', icon: 'success' })
uni.showToast({ title: '娣诲姞鎴愬姛', icon: 'success' })
}
closeModal()
@@ -166,9 +166,9 @@ function submitForm() {
<style scoped lang="scss">
.user-group-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
.content-card {
@@ -183,7 +183,7 @@ function submitForm() {
flex-direction: row;
}
/* 按钮样式 */
/* 鎸夐挳鏍峰紡 */
.btn {
height: 32px;
line-height: 32px;
@@ -219,7 +219,7 @@ function submitForm() {
padding: 0 12px;
}
/* 表格样式 */
/* 琛ㄦ牸鏍峰紡 */
.table-container {
border: 1px solid #f0f0f0;
border-radius: 2px;
@@ -291,7 +291,7 @@ function submitForm() {
opacity: 0.5;
}
/* 分页样式 */
/* 鍒嗛〉鏍峰紡 */
.pagination-row {
margin-top: 24px;
display: flex;
@@ -368,7 +368,7 @@ function submitForm() {
}
}
/* 弹窗样式 */
/* 寮圭獥鏍峰紡 */
.modal-mask {
position: fixed;
top: 0;
@@ -462,3 +462,4 @@ function submitForm() {
gap: 8px;
}
</style>

View File

@@ -1,78 +1,78 @@
<template>
<template>
<view class="user-label-page">
<view class="content-card">
<!-- 操作按钮行 -->
<!-- 鎿嶄綔鎸夐挳琛?-->
<view class="action-bar">
<button class="btn primary small" @click="onAddLabel">添加标签</button>
<button class="btn primary small" @click="onAddLabel">娣诲姞鏍囩</button>
</view>
<!-- 标签列表表格 -->
<!-- 鏍囩鍒楄〃琛ㄦ牸 -->
<view class="table-container">
<!-- 表头 -->
<!-- 琛ㄥご -->
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
<view class="col col-name"><text>标签名称</text></view>
<view class="col col-ops"><text>操作</text></view>
<view class="col col-name"><text>鏍囩鍚嶇О</text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<!-- 表格内容 -->
<!-- 琛ㄦ牸鍐呭 -->
<view class="table-body">
<view v-for="label in labelList" :key="label.id" class="table-row">
<view class="col col-id"><text>{{ label.id }}</text></view>
<view class="col col-name"><text>{{ label.name }}</text></view>
<view class="col col-ops">
<text class="op-link" @click="onEditLabel(label)">修改</text>
<text class="op-link" @click="onEditLabel(label)">淇敼</text>
<view class="op-divider">|</view>
<text class="op-link" @click="onDeleteLabel(label)">删除</text>
<text class="op-link" @click="onDeleteLabel(label)">鍒犻櫎</text>
</view>
</view>
</view>
</view>
<!-- 分页区域 -->
<!-- 鍒嗛〉鍖哄煙 -->
<view class="pagination-row">
<text class="total-text">{{ labelList.length }} 条</text>
<text class="total-text">鍏?{{ labelList.length }} 鏉?/text>
<view class="page-size-selector">
<text>15条/页</text>
<text class="arrow">▼</text>
<text>15鏉?椤?/text>
<text class="arrow">鈻?/text>
</view>
<view class="page-btns">
<view class="page-btn disabled"><text></text></view>
<view class="page-btn disabled"><text>鈥?/text></view>
<view class="page-btn active"><text>1</text></view>
<view class="page-btn disabled"><text></text></view>
<view class="page-btn disabled"><text>鈥?/text></view>
</view>
<view class="page-jump">
<text>前往</text>
<text>鍓嶅線</text>
<input class="jump-input" value="1" />
<text>页</text>
<text>椤?/text>
</view>
</view>
</view>
<!-- 添加/修改标签弹窗 -->
<!-- 娣诲姞/淇敼鏍囩寮圭獥 -->
<view class="modal-mask" v-if="showModal" @click="closeModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ modalTitle }}</text>
<text class="modal-close" @click="closeModal">×</text>
<text class="modal-close" @click="closeModal"></text>
</view>
<view class="modal-body">
<view class="form-item">
<view class="label-box">
<text class="required">*</text>
<text class="label">标签名称:</text>
<text class="label">鏍囩鍚嶇О锛?/text>
</view>
<input
class="form-input"
v-model="formData.name"
placeholder="请输入标签名称"
placeholder="璇疯緭鍏ユ爣绛惧悕绉?
autofocus
/>
</view>
</view>
<view class="modal-footer">
<button class="btn ghost" @click="closeModal">取消</button>
<button class="btn primary" @click="submitForm">确定</button>
<button class="btn ghost" @click="closeModal">鍙栨秷</button>
<button class="btn primary" @click="submitForm">纭畾</button>
</view>
</view>
</view>
@@ -82,26 +82,26 @@
<script setup lang="uts">
import { ref, reactive, computed } from 'vue'
// 标签数据
// 鏍囩鏁版嵁
const labelList = ref([
{ id: 1, name: '新客户' },
{ id: 2, name: '老客户' },
{ id: 3, name: '活跃客户' },
{ id: 4, name: '潜在客户' }
{ id: 1, name: '鏂板鎴? },
{ id: 2, name: '鑰佸鎴? },
{ id: 3, name: '娲昏穬瀹㈡埛' },
{ id: 4, name: '娼滃湪瀹㈡埛' }
])
// 弹窗状态
// 寮圭獥鐘舵€?
const showModal = ref(false)
const isEdit = ref(false)
const modalTitle = computed(() => isEdit.value ? '修改标签' : '添加标签')
const modalTitle = computed(() => isEdit.value ? '淇敼鏍囩' : '娣诲姞鏍囩')
// 表单数据
// 琛ㄥ崟鏁版嵁
const formData = reactive({
id: 0,
name: ''
})
// 添加标签
// 娣诲姞鏍囩
function onAddLabel() {
isEdit.value = false
formData.id = 0
@@ -109,7 +109,7 @@ function onAddLabel() {
showModal.value = true
}
// 修改标签
// 淇敼鏍囩
function onEditLabel(label: any) {
isEdit.value = true
formData.id = label.id
@@ -117,29 +117,29 @@ function onEditLabel(label: any) {
showModal.value = true
}
// 删除标签
// 鍒犻櫎鏍囩
function onDeleteLabel(label: any) {
uni.showModal({
title: '提示',
content: '确定要删除该标签吗?',
title: '鎻愮ず',
content: '纭畾瑕佸垹闄よ鏍囩鍚楋紵',
success: (res) => {
if (res.confirm) {
labelList.value = labelList.value.filter(item => item.id !== label.id)
uni.showToast({ title: '删除成功', icon: 'success' })
uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
}
}
})
}
// 关闭弹窗
// 鍏抽棴寮圭獥
function closeModal() {
showModal.value = false
}
// 提交表单
// 鎻愪氦琛ㄥ崟
function submitForm() {
if (!formData.name) {
uni.showToast({ title: '请输入标签名称', icon: 'none' })
uni.showToast({ title: '璇疯緭鍏ユ爣绛惧悕绉?, icon: 'none' })
return
}
@@ -148,14 +148,14 @@ function submitForm() {
if (index > -1) {
labelList.value[index].name = formData.name
}
uni.showToast({ title: '修改成功', icon: 'success' })
uni.showToast({ title: '淇敼鎴愬姛', icon: 'success' })
} else {
const newId = labelList.value.length > 0 ? Math.max(...labelList.value.map(g => g.id)) + 1 : 1
labelList.value.push({
id: newId,
name: formData.name
})
uni.showToast({ title: '添加成功', icon: 'success' })
uni.showToast({ title: '娣诲姞鎴愬姛', icon: 'success' })
}
closeModal()
@@ -164,9 +164,9 @@ function submitForm() {
<style scoped lang="scss">
.user-label-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
.content-card {
@@ -181,7 +181,7 @@ function submitForm() {
flex-direction: row;
}
/* 按钮样式 */
/* 鎸夐挳鏍峰紡 */
.btn {
height: 32px;
line-height: 32px;
@@ -217,7 +217,7 @@ function submitForm() {
padding: 0 12px;
}
/* 表格样式 */
/* 琛ㄦ牸鏍峰紡 */
.table-container {
border: 1px solid #f0f0f0;
border-radius: 2px;
@@ -289,7 +289,7 @@ function submitForm() {
opacity: 0.5;
}
/* 分页样式 */
/* 鍒嗛〉鏍峰紡 */
.pagination-row {
margin-top: 24px;
display: flex;
@@ -362,7 +362,7 @@ function submitForm() {
}
}
/* 弹窗样式 */
/* 寮圭獥鏍峰紡 */
.modal-mask {
position: fixed;
top: 0;
@@ -456,3 +456,4 @@ function submitForm() {
gap: 8px;
}
</style>

View File

@@ -1,44 +1,44 @@
<template>
<template>
<view class="admin-user-level">
<view class="content-body">
<!-- 顶部过滤栏 -->
<!-- 椤堕儴杩囨护鏍?-->
<view class="filter-card border-shadow">
<view class="filter-item">
<text class="label-txt">等级状态:</text>
<text class="label-txt">绛夌骇鐘舵€?</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow-down">▼</text>
<text class="select-val">璇烽€夋嫨</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="label-txt">等级名称:</text>
<input class="search-input" placeholder="请输入等级名称" v-model="filterName" />
<text class="label-txt">绛夌骇鍚嶇О:</text>
<input class="search-input" placeholder="璇疯緭鍏ョ瓑绾у悕绉? v-model="filterName" />
</view>
<view class="btn-query" @click="handleQuery">
<text class="query-txt">查询</text>
<text class="query-txt">鏌ヨ</text>
</view>
</view>
<!-- 主要内容区域 -->
<!-- 涓昏鍐呭鍖哄煙 -->
<view class="table-card border-shadow">
<view class="card-header">
<view class="btn-primary-blue" @click="handleAdd">
<text class="btn-txt">添加用户等级</text>
<text class="btn-txt">娣诲姞鐢ㄦ埛绛夌骇</text>
</view>
</view>
<!-- 数据表格 -->
<!-- 鏁版嵁琛ㄦ牸 -->
<view class="table-container">
<view class="table-header-row">
<view class="th" style="width: 80px;">ID</view>
<view class="th" style="width: 120px;">等级图标</view>
<view class="th" style="width: 150px;">等级背景图</view>
<view class="th" style="flex: 1;">等级名称</view>
<view class="th" style="width: 100px;">等级</view>
<view class="th" style="width: 120px;">享受折扣</view>
<view class="th" style="width: 150px;">经验值要求</view>
<view class="th" style="width: 120px;">是否显示</view>
<view class="th" style="width: 150px;">操作</view>
<view class="th" style="width: 120px;">绛夌骇鍥炬爣</view>
<view class="th" style="width: 150px;">绛夌骇鑳屾櫙鍥?/view>
<view class="th" style="flex: 1;">绛夌骇鍚嶇О</view>
<view class="th" style="width: 100px;">绛夌骇</view>
<view class="th" style="width: 120px;">浜彈鎶樻墸</view>
<view class="th" style="width: 150px;">缁忛獙鍊艰姹?/view>
<view class="th" style="width: 120px;">鏄惁鏄剧ず</view>
<view class="th" style="width: 150px;">鎿嶄綔</view>
</view>
<view class="table-body">
@@ -63,22 +63,22 @@
</view>
<view class="td" style="width: 150px;">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-link" @click="handleEdit(item)">缂栬緫</text>
<text class="op-split">|</text>
<text class="op-link text-danger">删除</text>
<text class="op-link text-danger">鍒犻櫎</text>
</view>
</view>
</view>
</view>
</view>
<!-- 分页 -->
<!-- 鍒嗛〉 -->
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">{{ total }} 条</text>
<text class="total-txt">鍏?{{ total }} 鏉?/text>
</view>
<view class="page-select">
<text class="page-val">15条/页 ▼</text>
<text class="page-val">15鏉?椤?鈻?/text>
</view>
<view class="page-btns">
<text class="p-btn disabled"><</text>
@@ -86,61 +86,61 @@
<text class="p-btn">></text>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<text class="jump-txt">鍓嶅線</text>
<input class="jump-input" placeholder="1" />
<text class="jump-txt">页</text>
<text class="jump-txt">椤?/text>
</view>
</view>
</view>
</view>
<!-- 抽屉弹窗 (Add/Edit) -->
<!-- 鎶藉眽寮圭獥 (Add/Edit) -->
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
<view class="drawer-header">
<text class="title-txt">{{ isEdit ? '编辑用户等级' : '添加用户等级' }}</text>
<text class="close-btn" @click="closeDrawer">×</text>
<text class="title-txt">{{ isEdit ? '缂栬緫鐢ㄦ埛绛夌骇' : '娣诲姞鐢ㄦ埛绛夌骇' }}</text>
<text class="close-btn" @click="closeDrawer"></text>
</view>
<scroll-view class="drawer-body" :scroll-y="true">
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">等级名称:</text></view>
<input class="input-base" v-model="form.name" placeholder="请输入等级名称" />
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鍚嶇О:</text></view>
<input class="input-base" v-model="form.name" placeholder="璇疯緭鍏ョ瓑绾у悕绉? />
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">等级权重:</text></view>
<input class="input-base" type="number" v-model="form.level" placeholder="等级权重越大等级越高" />
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鏉冮噸:</text></view>
<input class="input-base" type="number" v-model="form.level" placeholder="绛夌骇鏉冮噸瓒婂ぇ绛夌骇瓒婇珮" />
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">等级图标:</text></view>
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鍥炬爣:</text></view>
<view class="upload-placeholder">
<text class="up-ic">+</text>
<text class="up-txt">上传图标</text>
<text class="up-txt">涓婁紶鍥炬爣</text>
</view>
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">等级背景图:</text></view>
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鑳屾櫙鍥?</text></view>
<view class="upload-placeholder bg-up">
<text class="up-ic">+</text>
<text class="up-txt">上传背景图</text>
<text class="up-txt">涓婁紶鑳屾櫙鍥?/text>
</view>
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">享受折扣(%):</text></view>
<input class="input-base" type="number" v-model="form.discount" placeholder="请输入折扣,如:95" />
<view class="label-box"><text class="required">*</text><text class="label-txt">浜彈鎶樻墸(%):</text></view>
<input class="input-base" type="number" v-model="form.discount" placeholder="璇疯緭鍏ユ姌鎵o紝濡傦細95" />
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">经验值要求:</text></view>
<input class="input-base" type="number" v-model="form.experience" placeholder="请输入经验值要求" />
<view class="label-box"><text class="required">*</text><text class="label-txt">缁忛獙鍊艰姹?</text></view>
<input class="input-base" type="number" v-model="form.experience" placeholder="璇疯緭鍏ョ粡楠屽€艰姹? />
</view>
<view class="form-item">
<view class="label-box"><text class="label-txt">是否显示:</text></view>
<view class="label-box"><text class="label-txt">鏄惁鏄剧ず:</text></view>
<view :class="['switch-box', form.isShow ? 'active' : '']" @click="form.isShow = !form.isShow">
<view class="switch-dot"></view>
</view>
@@ -148,8 +148,8 @@
</scroll-view>
<view class="drawer-footer">
<view class="btn-cancel" @click="closeDrawer"><text class="btn-cancel-txt">取消</text></view>
<view class="btn-save" @click="handleSave"><text class="btn-save-txt">提交</text></view>
<view class="btn-cancel" @click="closeDrawer"><text class="btn-cancel-txt">鍙栨秷</text></view>
<view class="btn-save" @click="handleSave"><text class="btn-save-txt">鎻愪氦</text></view>
</view>
</view>
</view>
@@ -174,11 +174,11 @@ interface LevelItem {
const filterName = ref('')
const total = ref(5)
const levelList = ref<LevelItem[]>([
{ id: 1, name: 'V1', level: 1, iconBg: '#fdf6ec', iconSymbol: '👑', bgGradient: 'linear-gradient(to bottom right, #f5e6d3, #e8d5bc)', discount: 99, experience: 500, isShow: true },
{ id: 2, name: 'V2', level: 2, iconBg: '#ecf5ff', iconSymbol: '💎', bgGradient: 'linear-gradient(to bottom right, #d3e9f5, #bcd9e8)', discount: 97, experience: 1000, isShow: true },
{ id: 3, name: 'V3', level: 3, iconBg: '#f4f4f5', iconSymbol: '⭐', bgGradient: 'linear-gradient(to bottom right, #e3e3e3, #cbcbcb)', discount: 95, experience: 3000, isShow: true },
{ id: 4, name: 'V4', level: 4, iconBg: '#fef0f0', iconSymbol: '👑', bgGradient: 'linear-gradient(to bottom right, #f5dfd3, #e8c6bc)', discount: 93, experience: 8000, isShow: true },
{ id: 5, name: 'V5', level: 5, iconBg: '#f0f9eb', iconSymbol: '💠', bgGradient: 'linear-gradient(to bottom right, #d3e1f5, #bccce8)', discount: 70, experience: 15000, isShow: true }
{ id: 1, name: 'V1', level: 1, iconBg: '#fdf6ec', iconSymbol: '馃憫', bgGradient: 'linear-gradient(to bottom right, #f5e6d3, #e8d5bc)', discount: 99, experience: 500, isShow: true },
{ id: 2, name: 'V2', level: 2, iconBg: '#ecf5ff', iconSymbol: '馃拵', bgGradient: 'linear-gradient(to bottom right, #d3e9f5, #bcd9e8)', discount: 97, experience: 1000, isShow: true },
{ id: 3, name: 'V3', level: 3, iconBg: '#f4f4f5', iconSymbol: '猸?, bgGradient: 'linear-gradient(to bottom right, #e3e3e3, #cbcbcb)', discount: 95, experience: 3000, isShow: true },
{ id: 4, name: 'V4', level: 4, iconBg: '#fef0f0', iconSymbol: '馃憫', bgGradient: 'linear-gradient(to bottom right, #f5dfd3, #e8c6bc)', discount: 93, experience: 8000, isShow: true },
{ id: 5, name: 'V5', level: 5, iconBg: '#f0f9eb', iconSymbol: '馃挔', bgGradient: 'linear-gradient(to bottom right, #d3e1f5, #bccce8)', discount: 70, experience: 15000, isShow: true }
])
const showDrawer = ref(false)
@@ -252,7 +252,7 @@ const handleSave = () => {
gap: 20px;
}
/* 过滤栏 */
/* 杩囨护鏍?*/
.filter-card {
padding: 24px;
display: flex;
@@ -307,7 +307,7 @@ const handleSave = () => {
}
.query-txt { color: #fff; font-size: 14px; }
/* 表格区域 */
/* 琛ㄦ牸鍖哄煙 */
.table-card {
background-color: #fff;
display: flex;
@@ -355,7 +355,7 @@ const handleSave = () => {
.td-txt { font-size: 14px; color: #515a6e; }
/* 图标和背景预览 */
/* 鍥炬爣鍜岃儗鏅瑙?*/
.icon-circle {
width: 36px;
height: 36px;
@@ -374,7 +374,7 @@ const handleSave = () => {
border: 1px solid #eee;
}
/* Switch 开关复刻 */
/* Switch 寮€鍏冲鍒?*/
.switch-box {
width: 44px;
height: 22px;
@@ -402,7 +402,7 @@ const handleSave = () => {
.op-split { color: #e8eaec; margin: 0 5px; }
.text-danger { color: #ed4014; }
/* 分页 */
/* 鍒嗛〉 */
.pagination-footer {
padding: 20px;
display: flex;
@@ -447,7 +447,7 @@ const handleSave = () => {
.drawer-content {
width: 500px;
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
animation: slideIn 0.3s ease-out;
@@ -520,3 +520,4 @@ const handleSave = () => {
@keyframes fadeOut { from { background-color: rgba(0, 0, 0, 0.4); } to { background-color: rgba(0, 0, 0, 0); } }
</style>

View File

@@ -1,46 +1,46 @@
<template>
<template>
<view class="user-list-page">
<!-- 筛选面板 -->
<!-- 绛涢€夐潰鏉?-->
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">用户搜索:</text>
<text class="label">鐢ㄦ埛鎼滅储锛?/text>
<view class="input-group">
<view class="compact-select">
<text>请选择</text>
<text class="arrow">▼</text>
<text>璇烽€夋嫨</text>
<text class="arrow">鈻?/text>
</view>
<input class="filter-input" placeholder="请输入用户" />
<input class="filter-input" placeholder="璇疯緭鍏ョ敤鎴? />
</view>
</view>
<view class="filter-item">
<text class="label">用户等级:</text>
<text class="label">鐢ㄦ埛绛夌骇锛?/text>
<view class="filter-select">
<text class="select-placeholder">请选择用户等级</text>
<text class="arrow">▼</text>
<text class="select-placeholder">璇烽€夋嫨鐢ㄦ埛绛夌骇</text>
<text class="arrow">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="label">用户分组:</text>
<text class="label">鐢ㄦ埛鍒嗙粍锛?/text>
<view class="filter-select">
<text class="select-placeholder">请选择用户分组</text>
<text class="arrow">▼</text>
<text class="select-placeholder">璇烽€夋嫨鐢ㄦ埛鍒嗙粍</text>
<text class="arrow">鈻?/text>
</view>
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">搜索</button>
<button class="btn" @click="onReset">重置</button>
<text class="expand-btn">展开 </text>
<button class="btn primary" @click="onSearch">鎼滅储</button>
<button class="btn" @click="onReset">閲嶇疆</button>
<text class="expand-btn">灞曞紑 鈭?/text>
</view>
</view>
</view>
<!-- 内容卡片 -->
<!-- 鍐呭鍗$墖 -->
<view class="content-card">
<!-- 平台切换 Tabs -->
<!-- 骞冲彴鍒囨崲 Tabs -->
<view class="tabs-row">
<view
v-for="(tab, index) in tabs"
@@ -53,45 +53,45 @@
</view>
</view>
<!-- 操作按钮行 -->
<!-- 鎿嶄綔鎸夐挳琛?-->
<view class="action-bar">
<button class="btn primary small" @click="onAddUser">添加用户</button>
<button class="btn ghost small">发送优惠券</button>
<button class="btn ghost small">发送图文消息</button>
<button class="btn ghost small">批量设置分组</button>
<button class="btn ghost small">批量设置标签</button>
<button class="btn ghost small">导出</button>
<button class="btn primary small" @click="onAddUser">娣诲姞鐢ㄦ埛</button>
<button class="btn ghost small">鍙戦€佷紭鎯犲埜</button>
<button class="btn ghost small">鍙戦€佸浘鏂囨秷鎭?/button>
<button class="btn ghost small">鎵归噺璁剧疆鍒嗙粍</button>
<button class="btn ghost small">鎵归噺璁剧疆鏍囩</button>
<button class="btn ghost small">瀵煎嚭</button>
</view>
<!-- 用户列表表格 -->
<!-- 鐢ㄦ埛鍒楄〃琛ㄦ牸 -->
<view class="table-container">
<!-- 表头 -->
<!-- 琛ㄥご -->
<view class="table-header">
<view class="col col-check"><checkbox :checked="isAllChecked" /></view>
<view class="col col-expand"></view>
<view class="col col-id"><text>用户ID</text></view>
<view class="col col-avatar"><text>头像</text></view>
<view class="col col-name"><text>姓名</text></view>
<view class="col col-member"><text>付费会员</text></view>
<view class="col col-level"><text>用户等级</text></view>
<view class="col col-group"><text>分组</text></view>
<view class="col col-spread"><text>分销等级</text></view>
<view class="col col-phone"><text>手机号</text></view>
<view class="col col-type"><text>用户类型</text></view>
<view class="col col-id"><text>鐢ㄦ埛ID</text></view>
<view class="col col-avatar"><text>澶村儚</text></view>
<view class="col col-name"><text>濮撳悕</text></view>
<view class="col col-member"><text>浠樿垂浼氬憳</text></view>
<view class="col col-level"><text>鐢ㄦ埛绛夌骇</text></view>
<view class="col col-group"><text>鍒嗙粍</text></view>
<view class="col col-spread"><text>鍒嗛攢绛夌骇</text></view>
<view class="col col-phone"><text>鎵嬫満鍙?/text></view>
<view class="col col-type"><text>鐢ㄦ埛绫诲瀷</text></view>
<view class="col col-balance sortable">
<text>余额</text>
<text class="sort-icon">↕</text>
<text>浣欓</text>
<text class="sort-icon">鈫?/text>
</view>
<view class="col col-ops"><text>操作</text></view>
<view class="col col-ops"><text>鎿嶄綔</text></view>
</view>
<!-- 表格内容 -->
<!-- 琛ㄦ牸鍐呭 -->
<view class="table-body">
<view v-for="user in userList" :key="user.id" class="table-row"
:style="{ zIndex: activeDropdownId === user.id ? 1000 : 1 }"
>
<view class="col col-check"><checkbox :checked="user.checked" /></view>
<view class="col col-expand"><text class="expand-arrow"></text></view>
<view class="col col-expand"><text class="expand-arrow">鈥?/text></view>
<view class="col col-id"><text>{{ user.id }}</text></view>
<view class="col col-avatar">
<image class="avatar-img" :src="user.avatar" mode="aspectFill" />
@@ -100,7 +100,7 @@
<text class="name-text">{{ user.nickname }}</text>
</view>
<view class="col col-member">
<text :class="user.isMember === '是' ? 'status-yes' : 'status-no'">{{ user.isMember }}</text>
<text :class="user.isMember === '鏄? ? 'status-yes' : 'status-no'">{{ user.isMember }}</text>
</view>
<view class="col col-level"><text>{{ user.level }}</text></view>
<view class="col col-group"><text>{{ user.group }}</text></view>
@@ -109,7 +109,7 @@
<view class="col col-type"><text>{{ user.userType }}</text></view>
<view class="col col-balance"><text>{{ user.balance }}</text></view>
<view class="col col-ops">
<text class="op-link" @click.stop="onDetail(user)">详情</text>
<text class="op-link" @click.stop="onDetail(user)">璇︽儏</text>
<view class="op-divider">|</view>
<view class="more-hover-container"
@mouseover="activeDropdownId = user.id"
@@ -117,18 +117,18 @@
@click.stop="activeDropdownId = (activeDropdownId === user.id ? null : user.id)"
>
<view class="more-trigger pointer">
<text class="op-link">更多</text>
<text class="arrow"></text>
<text class="op-link">鏇村</text>
<text class="arrow">鈭?/text>
</view>
<view class="dropdown-list-box" v-if="activeDropdownId === user.id">
<view class="dropdown-arrow-top"></view>
<view class="dropdown-menu-list">
<text class="menu-item" @click.stop="uni.showToast({title:'修改余额', icon:'none'})">修改余额</text>
<text class="menu-item" @click.stop="uni.showToast({title:'修改积分', icon:'none'})">修改积分</text>
<text class="menu-item" @click.stop="uni.showToast({title:'赠送会员', icon:'none'})">赠送会员</text>
<text class="menu-item" @click.stop="uni.showToast({title:'设置分组', icon:'none'})">设置分组</text>
<text class="menu-item" @click.stop="uni.showToast({title:'设置标签', icon:'none'})">设置标签</text>
<text class="menu-item" @click.stop="uni.showToast({title:'修改上级推广人', icon:'none'})">修改上级推广人</text>
<text class="menu-item" @click.stop="uni.showToast({title:'淇敼浣欓', icon:'none'})">淇敼浣欓</text>
<text class="menu-item" @click.stop="uni.showToast({title:'淇敼绉垎', icon:'none'})">淇敼绉垎</text>
<text class="menu-item" @click.stop="uni.showToast({title:'璧犻€佷細鍛?, icon:'none'})">璧犻€佷細鍛?/text>
<text class="menu-item" @click.stop="uni.showToast({title:'璁剧疆鍒嗙粍', icon:'none'})">璁剧疆鍒嗙粍</text>
<text class="menu-item" @click.stop="uni.showToast({title:'璁剧疆鏍囩', icon:'none'})">璁剧疆鏍囩</text>
<text class="menu-item" @click.stop="uni.showToast({title:'淇敼涓婄骇鎺ㄥ箍浜?, icon:'none'})">淇敼涓婄骇鎺ㄥ箍浜?/text>
</view>
</view>
</view>
@@ -137,9 +137,9 @@
</view>
</view>
<!-- 分页区域 (模拟) -->
<!-- 鍒嗛〉鍖哄煙 (妯℃嫙) -->
<view class="pagination">
<text class="page-info">80834 条数据</text>
<text class="page-info">鍏?80834 鏉℃暟鎹?/text>
</view>
</view>
</view>
@@ -149,47 +149,47 @@
import { ref } from 'vue'
const activeTab = ref(0)
const tabs = ['全部', '微信公众号', '微信小程序', 'H5', 'PC', 'APP']
const tabs = ['鍏ㄩ儴', '寰俊鍏紬鍙?, '寰俊灏忕▼搴?, 'H5', 'PC', 'APP']
const isAllChecked = ref(false)
const activeDropdownId = ref<string | null>(null)
const userList = ref([
{ id: '77414', avatar: '/static/logo.png', nickname: '199****0268', isMember: '否', level: '无', group: '无', spreadLevel: '', phone: '199****0268', userType: '公众号', balance: '88888.00', checked: false },
{ id: '75311', avatar: '/static/logo.png', nickname: 'wljbhg', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100002.00', checked: false },
{ id: '75305', avatar: '/static/logo.png', nickname: '相见欢', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75296', avatar: '/static/logo.png', nickname: '..', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75293', avatar: '/static/logo.png', nickname: '钟(钏)华', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75289', avatar: '/static/logo.png', nickname: '小二上酒', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75257', avatar: '/static/logo.png', nickname: '5+7', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75226', avatar: '/static/logo.png', nickname: '慢步前行', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75211', avatar: '/static/logo.png', nickname: '难得糊涂', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false }
{ id: '77414', avatar: '/static/logo.png', nickname: '199****0268', isMember: '鍚?, level: '鏃?, group: '鏃?, spreadLevel: '', phone: '199****0268', userType: '鍏紬鍙?, balance: '88888.00', checked: false },
{ id: '75311', avatar: '/static/logo.png', nickname: 'wljbhg', isMember: '鍚?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100002.00', checked: false },
{ id: '75305', avatar: '/static/logo.png', nickname: '鐩歌娆?, isMember: '鍚?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false },
{ id: '75296', avatar: '/static/logo.png', nickname: '..', isMember: '鍚?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false },
{ id: '75293', avatar: '/static/logo.png', nickname: '閽?閽?鍗?, isMember: '鍚?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false },
{ id: '75289', avatar: '/static/logo.png', nickname: '灏忎簩涓婇厭', isMember: '鍚?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false },
{ id: '75257', avatar: '/static/logo.png', nickname: '5+7', isMember: '鏄?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false },
{ id: '75226', avatar: '/static/logo.png', nickname: '鎱㈡鍓嶈', isMember: '鏄?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false },
{ id: '75211', avatar: '/static/logo.png', nickname: '闅惧緱绯婃秱', isMember: '鍚?, level: '鏃?, group: 'A绫诲鎴?, spreadLevel: '', phone: '', userType: '鍏紬鍙?, balance: '100000.00', checked: false }
])
function onSearch() {
uni.showToast({ title: '搜索中...', icon: 'none' })
uni.showToast({ title: '鎼滅储涓?..', icon: 'none' })
}
function onReset() {
uni.showToast({ title: '已重置', icon: 'none' })
uni.showToast({ title: '宸查噸缃?, icon: 'none' })
}
function onAddUser() {
uni.showToast({ title: '添加用户', icon: 'none' })
uni.showToast({ title: '娣诲姞鐢ㄦ埛', icon: 'none' })
}
function onDetail(user: any) {
uni.showToast({ title: '查看用户: ' + user.id, icon: 'none' })
uni.showToast({ title: '鏌ョ湅鐢ㄦ埛: ' + user.id, icon: 'none' })
}
</script>
<style scoped lang="scss">
.user-list-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
/* 筛选卡片 */
/* 绛涢€夊崱鐗?*/
.filter-card {
background: #fff;
border-radius: 4px;
@@ -305,12 +305,12 @@ function onDetail(user: any) {
cursor: pointer;
}
/* 内容卡片 */
/* 鍐呭鍗$墖 */
.content-card {
background: #fff;
border-radius: 4px;
padding: 0;
overflow: visible; /* 必须 visible 以显示下拉菜单 */
overflow: visible; /* 蹇呴』 visible 浠ユ樉绀轰笅鎷夎彍鍗?*/
}
/* Tabs */
@@ -341,7 +341,7 @@ function onDetail(user: any) {
}
}
/* 操作栏 */
/* 鎿嶄綔鏍?*/
.action-bar {
padding: 16px 24px;
display: flex;
@@ -349,7 +349,7 @@ function onDetail(user: any) {
gap: 12px;
}
/* 表格 */
/* 琛ㄦ牸 */
.table-container {
padding: 0 24px 24px;
overflow: visible;
@@ -412,7 +412,7 @@ function onDetail(user: any) {
align-items: center;
justify-content: center;
height: 32px;
min-width: 46px; /* 增加点击和悬停范围 */
min-width: 46px; /* 澧炲姞鐐瑰嚮鍜屾偓鍋滆寖鍥?*/
padding: 0 4px;
cursor: pointer;
}
@@ -422,16 +422,16 @@ function onDetail(user: any) {
flex-direction: row;
align-items: center;
gap: 2px;
pointer-events: none; /* 确保事件冒泡到 container */
pointer-events: none; /* 纭繚浜嬩欢鍐掓场鍒?container */
.arrow { font-size: 10px; color: #2f54eb; margin-left: 2px; }
}
.dropdown-list-box {
position: absolute;
top: 30px;
right: -5px; /* 稍微向左移动一点 */
width: 140px; /* 增加宽度以容纳长文字 */
padding-top: 10px; /* 增加缓冲区防止鼠标移出 */
right: -5px; /* 绋嶅井鍚戝乏绉诲姩涓€鐐?*/
width: 140px; /* 澧炲姞瀹藉害浠ュ绾抽暱鏂囧瓧 */
padding-top: 10px; /* 澧炲姞缂撳啿鍖洪槻姝㈤紶鏍囩Щ鍑?*/
z-index: 9999;
}
@@ -530,3 +530,4 @@ function onDetail(user: any) {
color: #999;
}
</style>

View File

@@ -1,34 +1,34 @@
<template>
<template>
<view class="statistic-page">
<!-- 筛选栏 -->
<!-- 绛涢€夋爮 -->
<view class="filter-card">
<view class="filter-item">
<text class="filter-label">用户渠道:</text>
<text class="filter-label">鐢ㄦ埛娓犻亾锛?/text>
<view class="select-box">
<text class="select-text">全部</text>
<text class="select-arrow">▼</text>
<text class="select-text">鍏ㄩ儴</text>
<text class="select-arrow">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="filter-label">选择时间:</text>
<text class="filter-label">閫夋嫨鏃堕棿锛?/text>
<view class="date-picker-box">
<text class="date-icon">📅</text>
<text class="date-icon">馃搮</text>
<text class="date-text">2026/01/04 - 2026/02/02</text>
</view>
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">查询</button>
<button class="btn" @click="onExport">导出</button>
<button class="btn primary" @click="onSearch">鏌ヨ</button>
<button class="btn" @click="onExport">瀵煎嚭</button>
</view>
</view>
<!-- 用户概况卡片区 (使用 6-2-1 响应式网格) -->
<!-- 鐢ㄦ埛姒傚喌鍗$墖鍖?(浣跨敤 6-2-1 鍝嶅簲寮忕綉鏍? -->
<view class="section-card">
<view class="section-header">
<text class="section-title">用户概况</text>
<text class="info-icon">❓</text>
<text class="section-title">鐢ㄦ埛姒傚喌</text>
<text class="info-icon">鉂?/text>
</view>
<view class="kpi-grid-6">
@@ -40,22 +40,22 @@
<text class="kpi-label">{{ item.title }}</text>
<text class="kpi-value">{{ item.value }}</text>
<view class="kpi-meta">
<text class="meta-label">环比增长:</text>
<text class="meta-label">鐜瘮澧為暱锛?/text>
<text class="meta-value" :class="item.trend">
{{ item.percent }}
<text class="trend-icon">{{ item.trend === 'up' ? '▲' : '▼' }}</text>
<text class="trend-icon">{{ item.trend === 'up' ? '鈻? : '鈻? }}</text>
</text>
</view>
</view>
</view>
</view>
<!-- 图表区 -->
<!-- 鍥捐〃鍖?-->
<view class="chart-container">
<view class="chart-header">
<view class="header-left"></view>
<view class="header-right">
<text class="download-icon">📥</text>
<text class="download-icon">馃摜</text>
</view>
</view>
<AnalyticsMultiLineChart
@@ -66,7 +66,7 @@
</view>
</view>
<!-- 地域分布与性别比例 -->
<!-- 鍦板煙鍒嗗竷涓庢€у埆姣斾緥 -->
<view class="analysis-row">
<view class="map-col">
<AnalyticsUserMapTable />
@@ -85,39 +85,39 @@ import AnalyticsUserMapTable from '@/components/analytics/AnalyticsUserMapTable.
import AnalyticsUserGenderSection from '@/components/analytics/AnalyticsUserGenderSection.uvue'
const kpiData = [
{ title: '累计用户', value: '80887', percent: '0.79%', trend: 'up', icon: '👥', bg: '#efebff' },
{ title: '访客数', value: '1076', percent: '11.65%', trend: 'down', icon: '👤', bg: '#e8f4ff' },
{ title: '浏览量', value: '8843', percent: '12.09%', trend: 'down', icon: '📁', bg: '#e6fff1' },
{ title: '新增用户数', value: '635', percent: '14.65%', trend: 'down', icon: '👤', bg: '#fff7e6' },
{ title: '成交用户数', value: '122', percent: '0.81%', trend: 'down', icon: '👥', bg: '#f2f0ff' },
{ title: '付费会员数', value: '76', percent: '13.63%', trend: 'down', icon: '💎', bg: '#f2f0ff' }
{ title: '绱鐢ㄦ埛', value: '80887', percent: '0.79%', trend: 'up', icon: '馃懃', bg: '#efebff' },
{ title: '璁垮鏁?, value: '1076', percent: '11.65%', trend: 'down', icon: '馃懁', bg: '#e8f4ff' },
{ title: '娴忚閲?, value: '8843', percent: '12.09%', trend: 'down', icon: '馃搧', bg: '#e6fff1' },
{ title: '鏂板鐢ㄦ埛鏁?, value: '635', percent: '14.65%', trend: 'down', icon: '馃懁', bg: '#fff7e6' },
{ title: '鎴愪氦鐢ㄦ埛鏁?, value: '122', percent: '0.81%', trend: 'down', icon: '馃懃', bg: '#f2f0ff' },
{ title: '浠樿垂浼氬憳鏁?, value: '76', percent: '13.63%', trend: 'down', icon: '馃拵', bg: '#f2f0ff' }
]
const chartData = {
x: ['01-04', '01-05', '01-06', '01-07', '01-08', '01-09', '01-10', '01-11', '01-12', '01-13', '01-14', '01-15', '01-16', '01-17', '01-18', '01-19', '01-20', '01-21', '01-22', '01-23', '01-24', '01-25', '01-26', '01-27', '01-28', '01-29', '01-30', '01-31', '02-01', '02-02'],
series: [
{ name: '新增用户数', color: '#1890ff', data: [40, 30, 25, 30, 22, 10, 20, 32, 28, 15, 8, 12, 18, 22, 15, 12, 25, 30, 28, 25, 35, 20, 18, 22, 20, 15, 10, 8, 15, 38] },
{ name: '访客数', color: '#52c41a', data: [70, 75, 65, 55, 65, 50, 45, 35, 50, 68, 72, 65, 50, 48, 55, 65, 75, 62, 58, 85, 70, 55, 48, 58, 65, 72, 68, 60, 45, 50] },
{ name: '浏览量', color: '#fa8c16', data: [520, 500, 420, 280, 580, 180, 220, 100, 180, 450, 500, 400, 320, 340, 150, 280, 450, 320, 440, 460, 320, 260, 320, 280, 380, 400, 320, 330, 250, 300] },
{ name: '成交用户数', color: '#722ed1', data: [15, 12, 10, 8, 18, 5, 8, 4, 6, 12, 15, 10, 8, 9, 4, 10, 12, 8, 10, 12, 8, 6, 10, 8, 12, 14, 10, 8, 5, 8] },
{ name: '新增付费用户数', color: '#f5222d', data: [5, 4, 3, 2, 6, 1, 2, 1, 2, 4, 5, 3, 2, 3, 1, 3, 4, 2, 3, 4, 2, 2, 3, 2, 4, 5, 3, 2, 1, 3] }
{ name: '鏂板鐢ㄦ埛鏁?, color: '#1890ff', data: [40, 30, 25, 30, 22, 10, 20, 32, 28, 15, 8, 12, 18, 22, 15, 12, 25, 30, 28, 25, 35, 20, 18, 22, 20, 15, 10, 8, 15, 38] },
{ name: '璁垮鏁?, color: '#52c41a', data: [70, 75, 65, 55, 65, 50, 45, 35, 50, 68, 72, 65, 50, 48, 55, 65, 75, 62, 58, 85, 70, 55, 48, 58, 65, 72, 68, 60, 45, 50] },
{ name: '娴忚閲?, color: '#fa8c16', data: [520, 500, 420, 280, 580, 180, 220, 100, 180, 450, 500, 400, 320, 340, 150, 280, 450, 320, 440, 460, 320, 260, 320, 280, 380, 400, 320, 330, 250, 300] },
{ name: '鎴愪氦鐢ㄦ埛鏁?, color: '#722ed1', data: [15, 12, 10, 8, 18, 5, 8, 4, 6, 12, 15, 10, 8, 9, 4, 10, 12, 8, 10, 12, 8, 6, 10, 8, 12, 14, 10, 8, 5, 8] },
{ name: '鏂板浠樿垂鐢ㄦ埛鏁?, color: '#f5222d', data: [5, 4, 3, 2, 6, 1, 2, 1, 2, 4, 5, 3, 2, 3, 1, 3, 4, 2, 3, 4, 2, 2, 3, 2, 4, 5, 3, 2, 1, 3] }
]
}
function onSearch() {
uni.showToast({ title: '搜索中...' })
uni.showToast({ title: '鎼滅储涓?..' })
}
function onExport() {
uni.showToast({ title: '导出中...' })
uni.showToast({ title: '瀵煎嚭涓?..' })
}
</script>
<style scoped>
.statistic-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
/* padding removed */
}
.filter-card {
@@ -226,7 +226,7 @@ function onExport() {
font-size: 14px;
}
/* kpi-row 已废弃,采用全局 kpi-grid-6 */
/* kpi-row 宸插簾寮冿紝閲囩敤鍏ㄥ眬 kpi-grid-6 */
.kpi-card {
display: flex;
flex-direction: row;
@@ -329,3 +329,4 @@ function onExport() {
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click.self="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'优惠券效果分析'"
:title="'浼樻儬鍒告晥鏋滃垎鏋?"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,18 +16,18 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 时间维度筛选(快捷 + 自定义) -->
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -43,8 +43,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
<AnalyticsDateRangePicker
@@ -55,67 +54,67 @@
@clear="onDateRangeClear"
/>
<!-- KPI 指标卡片 -->
<!-- KPI 鎸囨爣鍗$墖 -->
<view class="kpi-grid">
<view class="kpi-card">
<text class="kpi-label">发放总数</text>
<text class="kpi-label">鍙戞斁鎬绘暟</text>
<text class="kpi-value">{{ formatInt(couponData.total_issued) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(couponData.issued_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(couponData.issued_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">使用数量</text>
<text class="kpi-label">浣跨敤鏁伴噺</text>
<text class="kpi-value">{{ formatInt(couponData.total_used) }}</text>
<text class="kpi-meta">使用率:{{ formatPct(couponData.usage_rate) }}</text>
<text class="kpi-meta">浣跨敤鐜囷細{{ formatPct(couponData.usage_rate) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">GMV 提升</text>
<text class="kpi-value">¥{{ formatMoney(couponData.gmv_increase) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(couponData.gmv_growth) }}</text>
<text class="kpi-label">GMV 鎻愬崌</text>
<text class="kpi-value">{{ formatMoney(couponData.gmv_increase) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(couponData.gmv_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">ROI</text>
<text class="kpi-value">{{ formatPct(couponData.roi) }}</text>
<text class="kpi-meta">投入产出比</text>
<text class="kpi-meta">鎶曞叆浜у嚭姣?/text>
</view>
</view>
<!-- 优惠券类型分析 -->
<!-- 浼樻儬鍒哥被鍨嬪垎鏋?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">优惠券类型分析</text>
<text class="card-desc">8种券类型:满减券、折扣券、免运费券、新人券、会员券、品类券、商家券、限时券</text>
<text class="card-title">浼樻儬鍒哥被鍨嬪垎鏋?/text>
<text class="card-desc">8绉嶅埜绫诲瀷锛氭弧鍑忓埜銆佹姌鎵e埜銆佸厤杩愯垂鍒搞€佹柊浜哄埜銆佷細鍛樺埜銆佸搧绫诲埜銆佸晢瀹跺埜銆侀檺鏃跺埜</text>
</view>
<EChartsView class="chart-box" :option="typeChartOption" />
</view>
<!-- 发放渠道效果 -->
<!-- 鍙戞斁娓犻亾鏁堟灉 -->
<view class="card">
<view class="card-head">
<text class="card-title">发放渠道效果</text>
<text class="card-desc">主动领取、自动发放、活动赠送、邀请奖励、客服赠送、积分兑换</text>
<text class="card-title">鍙戞斁娓犻亾鏁堟灉</text>
<text class="card-desc">涓诲姩棰嗗彇銆佽嚜鍔ㄥ彂鏀俱€佹椿鍔ㄨ禒閫併€侀個璇峰鍔便€佸鏈嶈禒閫併€佺Н鍒嗗厬鎹?/text>
</view>
<EChartsView class="chart-box" :option="channelChartOption" />
</view>
<!-- 优惠券使用趋势 -->
<!-- 浼樻儬鍒镐娇鐢ㄨ秼鍔?-->
<view class="card">
<view class="card-head">
<text class="card-title">优惠券使用趋势</text>
<text class="card-desc">{{ selectedPeriodText }} · 发放 vs 使用</text>
<text class="card-title">浼樻儬鍒镐娇鐢ㄨ秼鍔?/text>
<text class="card-desc">{{ selectedPeriodText }} 路 鍙戞斁 vs 浣跨敤</text>
</view>
<EChartsView class="chart-box" :option="trendChartOption" />
</view>
<!-- 优惠券转化效果 -->
<!-- 浼樻儬鍒歌浆鍖栨晥鏋?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">优惠券转化效果</text>
<text class="card-desc">GMV提升、订单增长</text>
<text class="card-title">浼樻儬鍒歌浆鍖栨晥鏋?/text>
<text class="card-desc">GMV鎻愬崌銆佽鍗曞闀?/text>
</view>
<EChartsView class="chart-box" :option="conversionChartOption" />
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -147,10 +146,10 @@ const showSidebarMenu = ref(false)
const currentPath = ref('/pages/mall/analytics/coupon-analysis')
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const couponData = ref<CouponData>({
@@ -168,7 +167,7 @@ const channelChartOption = ref({} as any)
const trendChartOption = ref({} as any)
const conversionChartOption = ref({} as any)
// 原始数据
// 鍘熷鏁版嵁
const _typeRows = ref<Array<UTSJSONObject>>([])
const _channelRows = ref<Array<UTSJSONObject>>([])
const _trendRows = ref<Array<UTSJSONObject>>([])
@@ -176,7 +175,7 @@ const _conversionRows = ref<Array<UTSJSONObject>>([])
const selectedPeriodText = computed(() => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
onLoad(() => {
@@ -246,7 +245,7 @@ async function loadCouponData() {
console.error('loadCouponData failed:', e)
updateTime()
buildChartOptions()
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '优惠券分析数据加载失败' }), icon: 'none' })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '浼樻儬鍒稿垎鏋愭暟鎹姞杞藉け璐? }), icon: 'none' })
}
}
@@ -278,13 +277,13 @@ function onDateRangeClear() {
function refreshData() {
loadCouponData()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
@@ -297,13 +296,13 @@ function updateTime() {
function formatInt(n: number): string {
const v = isFinite(n) ? Math.round(n) : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toString()
}
function formatMoney(n: number): string {
const v = isFinite(n) ? n : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toFixed(0)
}
@@ -319,8 +318,7 @@ function buildChartOptions() {
const trendRows = _trendRows.value
const convRows = _conversionRows.value
// 1) 券类型分析
const typeNames: string[] = []
// 1) 鍒哥被鍨嬪垎鏋? const typeNames: string[] = []
const typeIssued: number[] = []
const typeUsed: number[] = []
const typeUsageRate: number[] = []
@@ -328,15 +326,15 @@ function buildChartOptions() {
for (let i = 0; i < typeRows.length; i++) {
const r = typeRows[i]
const t = r.getNumber('coupon_type') ?? 0
let label = '未知'
if (t === 1) label = '满减券'
else if (t === 2) label = '折扣券'
else if (t === 3) label = '免运费券'
else if (t === 4) label = '新人券'
else if (t === 5) label = '会员券'
else if (t === 6) label = '品类券'
else if (t === 7) label = '商家券'
else if (t === 8) label = '限时券'
let label = '鏈煡'
if (t === 1) label = '婊″噺鍒?
else if (t === 2) label = '鎶樻墸鍒?
else if (t === 3) label = '鍏嶈繍璐瑰埜'
else if (t === 4) label = '鏂颁汉鍒?
else if (t === 5) label = '浼氬憳鍒?
else if (t === 6) label = '鍝佺被鍒?
else if (t === 7) label = '鍟嗗鍒?
else if (t === 8) label = '闄愭椂鍒?
typeNames.push(label)
typeIssued.push(r.getNumber('total_issued') ?? 0)
typeUsed.push(r.getNumber('total_used') ?? 0)
@@ -346,7 +344,7 @@ function buildChartOptions() {
typeChartOption.value = {
tooltip: { trigger: 'axis' },
legend: {
data: ['发放数量', '使用数量', '使用率'],
data: ['鍙戞斁鏁伴噺', '浣跨敤鏁伴噺', '浣跨敤鐜?],
top: 'bottom'
},
grid: { left: 40, right: 40, top: 40, bottom: 60 },
@@ -356,26 +354,26 @@ function buildChartOptions() {
axisLabel: { interval: 0, rotate: 20 }
},
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '使用率', min: 0, max: 100, position: 'right' }
{ type: 'value', name: '鏁伴噺' },
{ type: 'value', name: '浣跨敤鐜?, min: 0, max: 100, position: 'right' }
],
series: [
{
name: '发放数量',
name: '鍙戞斁鏁伴噺',
type: 'bar',
data: typeIssued,
barMaxWidth: 22,
itemStyle: { color: '#3b82f6' }
},
{
name: '使用数量',
name: '浣跨敤鏁伴噺',
type: 'bar',
data: typeUsed,
barMaxWidth: 22,
itemStyle: { color: '#22c55e' }
},
{
name: '使用率',
name: '浣跨敤鐜?,
type: 'line',
yAxisIndex: 1,
smooth: true,
@@ -389,7 +387,7 @@ function buildChartOptions() {
]
}
// 2) 发放渠道效果
// 2) 鍙戞斁娓犻亾鏁堟灉
const channelNames: string[] = []
const channelIssued: number[] = []
const channelUsed: number[] = []
@@ -398,13 +396,13 @@ function buildChartOptions() {
const r = channelRows[i]
const ch = r.getString('channel') ?? ''
let chLabel = ch
if (ch === 'manual') chLabel = '主动领取'
else if (ch === 'auto') chLabel = '自动发放'
else if (ch === 'campaign') chLabel = '活动赠送'
else if (ch === 'invite') chLabel = '邀请奖励'
else if (ch === 'cs') chLabel = '客服赠送'
else if (ch === 'points') chLabel = '积分兑换'
else if (ch.trim() === '') chLabel = '未知'
if (ch === 'manual') chLabel = '涓诲姩棰嗗彇'
else if (ch === 'auto') chLabel = '鑷姩鍙戞斁'
else if (ch === 'campaign') chLabel = '娲诲姩璧犻€?
else if (ch === 'invite') chLabel = '閭€璇峰鍔?
else if (ch === 'cs') chLabel = '瀹㈡湇璧犻€?
else if (ch === 'points') chLabel = '绉垎鍏戞崲'
else if (ch.trim() === '') chLabel = '鏈煡'
channelNames.push(chLabel)
channelIssued.push(r.getNumber('total_issued') ?? 0)
channelUsed.push(r.getNumber('total_used') ?? 0)
@@ -412,17 +410,17 @@ function buildChartOptions() {
channelChartOption.value = {
tooltip: { trigger: 'axis' },
legend: { data: ['发放数量', '使用数量'], top: 'bottom' },
legend: { data: ['鍙戞斁鏁伴噺', '浣跨敤鏁伴噺'], top: 'bottom' },
grid: { left: 80, right: 30, top: 20, bottom: 60 },
xAxis: { type: 'value' },
yAxis: { type: 'category', data: channelNames },
series: [
{ name: '发放数量', type: 'bar', data: channelIssued },
{ name: '使用数量', type: 'bar', data: channelUsed }
{ name: '鍙戞斁鏁伴噺', type: 'bar', data: channelIssued },
{ name: '浣跨敤鏁伴噺', type: 'bar', data: channelUsed }
]
}
// 3) 使用趋势
// 3) 浣跨敤瓒嬪娍
const trendDays: string[] = []
const trendIssued: number[] = []
const trendUsed: number[] = []
@@ -437,17 +435,17 @@ function buildChartOptions() {
trendChartOption.value = {
tooltip: { trigger: 'axis' },
legend: { data: ['发放数量', '使用数量'], top: 'bottom' },
legend: { data: ['鍙戞斁鏁伴噺', '浣跨敤鏁伴噺'], top: 'bottom' },
grid: { left: 40, right: 20, top: 40, bottom: 60 },
xAxis: { type: 'category', data: trendDays },
yAxis: { type: 'value', name: '数量' },
yAxis: { type: 'value', name: '鏁伴噺' },
series: [
{ name: '发放数量', type: 'bar', data: trendIssued },
{ name: '使用数量', type: 'line', smooth: true, data: trendUsed }
{ name: '鍙戞斁鏁伴噺', type: 'bar', data: trendIssued },
{ name: '浣跨敤鏁伴噺', type: 'line', smooth: true, data: trendUsed }
]
}
// 4) 转化效果
// 4) 杞寲鏁堟灉
const convNames: string[] = []
const convWith: number[] = []
const convWithout: number[] = []
@@ -456,10 +454,10 @@ function buildChartOptions() {
const r = convRows[i]
const metric = r.getString('metric') ?? ''
let metricLabel = metric
if (metric === 'GMV') metricLabel = 'GMV(成交额)'
else if (metric === 'orders') metricLabel = '订单数'
else if (metric === 'avg_order_amount') metricLabel = '客单价'
else if (metric.trim() === '') metricLabel = '未知'
if (metric === 'GMV') metricLabel = 'GMV锛堟垚浜ら锛?
else if (metric === 'orders') metricLabel = '璁㈠崟鏁?
else if (metric === 'avg_order_amount') metricLabel = '瀹㈠崟浠?
else if (metric.trim() === '') metricLabel = '鏈煡'
convNames.push(metricLabel)
convWith.push(r.getNumber('with_coupon') ?? 0)
convWithout.push(r.getNumber('without_coupon') ?? 0)
@@ -467,13 +465,13 @@ function buildChartOptions() {
conversionChartOption.value = {
tooltip: { trigger: 'axis' },
legend: { data: ['使用优惠券', '未使用优惠券'], top: 'bottom' },
legend: { data: ['浣跨敤浼樻儬鍒?, '鏈娇鐢ㄤ紭鎯犲埜'], top: 'bottom' },
grid: { left: 40, right: 20, top: 20, bottom: 60 },
xAxis: { type: 'category', data: convNames },
yAxis: { type: 'value' },
series: [
{ name: '使用优惠券', type: 'bar', data: convWith },
{ name: '未使用优惠券', type: 'bar', data: convWithout }
{ name: '浣跨敤浼樻儬鍒?, type: 'bar', data: convWith },
{ name: '鏈娇鐢ㄤ紭鎯犲埜', type: 'bar', data: convWithout }
]
}
}
@@ -494,13 +492,13 @@ function handleMenu() {
showSidebarMenu.value = true
}
// 模拟的 TopBar 事件处理
function handleSearch() { uni.showToast({ title: '搜索', icon: 'none' }) }
function handleNotification() { uni.showToast({ title: '通知', icon: 'none' }) }
function handleFullscreen() { uni.showToast({ title: '全屏', icon: 'none' }) }
function handleMobile() { uni.showToast({ title: '移动端', icon: 'none' }) }
function handleDropdown() { uni.showToast({ title: '下拉菜单', icon: 'none' }) }
function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
// 妯℃嫙鐨?TopBar 浜嬩欢澶勭悊
function handleSearch() { uni.showToast({ title: '鎼滅储', icon: 'none' }) }
function handleNotification() { uni.showToast({ title: '閫氱煡', icon: 'none' }) }
function handleFullscreen() { uni.showToast({ title: '鍏ㄥ睆', icon: 'none' }) }
function handleMobile() { uni.showToast({ title: '绉诲姩绔?, icon: 'none' }) }
function handleDropdown() { uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' }) }
function handleSettings() { uni.showToast({ title: '璁剧疆', icon: 'none' }) }
</script>
@@ -510,7 +508,7 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -522,18 +520,18 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
/* 椤堕儴鏍?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -666,7 +664,7 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
color: #111;
}
/* 时间维度 tabs */
/* 鏃堕棿缁村害 tabs */
.tabs {
margin-top: 12px;
display: flex;
@@ -696,7 +694,7 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
color: #fff;
}
/* KPI 网格 */
/* KPI 缃戞牸 */
.kpi-grid {
margin-top: 12px;
display: flex;
@@ -733,7 +731,7 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
color: rgba(0,0,0,0.55);
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -772,7 +770,7 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
height: 360px;
}
/* 响应式 */
/* 鍝嶅簲寮?*/
@media screen and (min-width: 960px) {
.kpi-card {
flex: 1 1 calc(25% - 9px);
@@ -780,7 +778,7 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
}
}
/* 响应式:窄屏时全屏显示 */
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
@@ -806,3 +804,4 @@ function handleSettings() { uni.showToast({ title: '设置', icon: 'none' }) }
}
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<template>
<view class="page" @click="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'自定义报表'"
:lastUpdateTime="'创建和管理您的专属报表'"
:title="'鑷畾涔夋姤琛?"
:lastUpdateTime="'鍒涘缓鍜岀鐞嗘偍鐨勪笓灞炴姤琛?"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@refresh="refreshData"
@@ -16,90 +16,90 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 顶部操作区:新建报表 -->
<!-- 椤堕儴鎿嶄綔鍖猴細鏂板缓鎶ヨ〃 -->
<view class="toolbar">
<view class="toolbar-left">
<text class="toolbar-title">我的自定义报表</text>
<text class="toolbar-subtitle">按需组合指标和时间范围,生成专属报表</text>
<text class="toolbar-title">鎴戠殑鑷畾涔夋姤琛?/text>
<text class="toolbar-subtitle">鎸夐渶缁勫悎鎸囨爣鍜屾椂闂磋寖鍥达紝鐢熸垚涓撳睘鎶ヨ〃</text>
</view>
<view class="toolbar-right">
<button class="btn-primary" @click.stop="createReport"> 新建报表</button>
<button class="btn-primary" @click.stop="createReport">锛?鏂板缓鎶ヨ〃</button>
</view>
</view>
<!-- 报表列表 / 空状态 -->
<!-- 鎶ヨ〃鍒楄〃 / 绌虹姸鎬?-->
<view v-if="reports.length > 0" class="report-list">
<view v-for="report in reports" :key="report.id" class="report-card" @click="openReport(report)">
<view class="report-header">
<text class="report-title">{{ report.name }}</text>
<view class="report-actions">
<view class="action-btn" @click.stop="editReport(report)">
<text class="icon">✏️</text>
<text class="icon">鉁忥笍</text>
</view>
<view class="action-btn" @click.stop="deleteReport(report)">
<text class="icon">🗑️</text>
<text class="icon">馃棏锔?/text>
</view>
</view>
</view>
<text class="report-desc">{{ report.description || '点击进入报表详情查看数据' }}</text>
<text class="report-desc">{{ report.description || '鐐瑰嚮杩涘叆鎶ヨ〃璇︽儏鏌ョ湅鏁版嵁' }}</text>
<view class="report-meta">
<text class="meta-item">图表周期:{{ report.period || '自定义' }}</text>
<text class="meta-item">最近更新:{{ report.updated_at || '-' }}</text>
<text class="meta-item">鍥捐〃鍛ㄦ湡锛歿{ report.period || '鑷畾涔? }}</text>
<text class="meta-item">鏈€杩戞洿鏂帮細{{ report.updated_at || '-' }}</text>
</view>
</view>
</view>
<view v-else class="empty-state">
<text v-if="isLoggedIn" class="empty-title">暂无自定义报表</text>
<text v-else class="empty-title">请先登录</text>
<text v-if="isLoggedIn" class="empty-desc">点击下方按钮创建第一份报表,用于复用常看的指标组合。</text>
<text v-else class="empty-desc">创建自定义报表需要登录账号,请先登录后再使用此功能。</text>
<button v-if="isLoggedIn" class="btn-primary" @click.stop="createReport"> 新建报表</button>
<button v-else class="btn-primary" @click.stop="goToLogin">前往登录</button>
<text v-if="isLoggedIn" class="empty-title">鏆傛棤鑷畾涔夋姤琛?/text>
<text v-else class="empty-title">璇峰厛鐧诲綍</text>
<text v-if="isLoggedIn" class="empty-desc">鐐瑰嚮涓嬫柟鎸夐挳鍒涘缓绗竴浠芥姤琛紝鐢ㄤ簬澶嶇敤甯哥湅鐨勬寚鏍囩粍鍚堛€?/text>
<text v-else class="empty-desc">鍒涘缓鑷畾涔夋姤琛ㄩ渶瑕佺櫥褰曡处鍙凤紝璇峰厛鐧诲綍鍚庡啀浣跨敤姝ゅ姛鑳姐€?/text>
<button v-if="isLoggedIn" class="btn-primary" @click.stop="createReport">锛?鏂板缓鎶ヨ〃</button>
<button v-else class="btn-primary" @click.stop="goToLogin">鍓嶅線鐧诲綍</button>
</view>
<!-- 新建报表对话框 -->
<!-- 鏂板缓鎶ヨ〃瀵硅瘽妗?-->
<view class="modal" v-if="showCreateModal" @click.stop>
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ editingReport ? '编辑报表' : '新建报表' }}</text>
<text class="modal-title">{{ editingReport ? '缂栬緫鎶ヨ〃' : '鏂板缓鎶ヨ〃' }}</text>
<view class="modal-close" @click="closeModal">
<text class="icon">✕</text>
<text class="icon">鉁?/text>
</view>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">报表名称</text>
<text class="form-label">鎶ヨ〃鍚嶇О</text>
<input
class="form-input"
v-model="reportForm.name"
placeholder="请输入报表名称1-50个字符"
placeholder="璇疯緭鍏ユ姤琛ㄥ悕绉帮紙1-50涓瓧绗︼級"
@input="onNameInput"
/>
<text v-if="formErrors.name" class="form-error">{{ formErrors.name }}</text>
</view>
<view class="form-item">
<text class="form-label">报表描述</text>
<text class="form-label">鎶ヨ〃鎻忚堪</text>
<textarea
class="form-textarea"
v-model="reportForm.description"
placeholder="选填最多200个字符"
placeholder="閫夊~锛屾渶澶?00涓瓧绗?
@input="onDescriptionInput"
></textarea>
<text v-if="formErrors.description" class="form-error">{{ formErrors.description }}</text>
</view>
<view class="form-item">
<text class="form-label">选择指标</text>
<text class="form-label">閫夋嫨鎸囨爣</text>
<view class="metric-list">
<view
v-for="m in availableMetrics"
@@ -114,7 +114,7 @@
<text v-if="formErrors.metrics" class="form-error">{{ formErrors.metrics }}</text>
</view>
<view class="form-item">
<text class="form-label">时间维度</text>
<text class="form-label">鏃堕棿缁村害</text>
<view class="period-list">
<view
v-for="p in timePeriods"
@@ -129,7 +129,7 @@
<text v-if="formErrors.period" class="form-error">{{ formErrors.period }}</text>
</view>
<view class="form-item">
<text class="form-label">图表类型</text>
<text class="form-label">鍥捐〃绫诲瀷</text>
<view class="chart-type-list">
<view
v-for="t in chartTypes"
@@ -145,13 +145,13 @@
</view>
</view>
<view class="modal-footer">
<view class="btn btn-cancel" @click="closeModal">取消</view>
<view class="btn btn-primary" @click="saveReport">保存</view>
<view class="btn btn-cancel" @click="closeModal">鍙栨秷</view>
<view class="btn btn-primary" @click="saveReport">淇濆瓨</view>
</view>
</view>
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -202,31 +202,31 @@ const formErrors = reactive<ReportFormErrors>({
const availableMetrics = ref<Array<Metric>>([
{ key: 'gmv', label: 'GMV' },
{ key: 'orders', label: '订单数' },
{ key: 'users', label: '用户数' },
{ key: 'conversion', label: '转化率' },
{ key: 'avg_order', label: '客单价' },
{ key: 'repurchase', label: '复购率' }
{ key: 'orders', label: '璁㈠崟鏁? },
{ key: 'users', label: '鐢ㄦ埛鏁? },
{ key: 'conversion', label: '杞寲鐜? },
{ key: 'avg_order', label: '瀹㈠崟浠? },
{ key: 'repurchase', label: '澶嶈喘鐜? }
])
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const chartTypes = ref<Array<ChartType>>([
{ value: 'line', label: '折线图' },
{ value: 'bar', label: '柱状图' },
{ value: 'pie', label: '饼图' },
{ value: 'area', label: '面积图' },
{ value: 'combo', label: '组合图' }
{ value: 'line', label: '鎶樼嚎鍥? },
{ value: 'bar', label: '鏌辩姸鍥? },
{ value: 'pie', label: '楗煎浘' },
{ value: 'area', label: '闈㈢Н鍥? },
{ value: 'combo', label: '缁勫悎鍥? }
])
onLoad(() => {
currentPath.value = '/pages/mall/analytics/custom-report'
if (!ensureAnalyticsLogin({ toastTitle: '请先登录后使用自定义报表' })) return
if (!ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庝娇鐢ㄨ嚜瀹氫箟鎶ヨ〃' })) return
loadReports()
})
@@ -264,7 +264,7 @@ async function loadReports() {
reports.splice(0, reports.length, ...list)
} catch (e) {
console.error('loadReports failed', e)
uni.showToast({ title: '报表加载失败', icon: 'none' })
uni.showToast({ title: '鎶ヨ〃鍔犺浇澶辫触', icon: 'none' })
}
}
@@ -306,8 +306,8 @@ function editReport(report: CustomReport) {
function deleteReport(report: CustomReport) {
uni.showModal({
title: '确认删除',
content: `确定要删除报表"${report.name}"吗?`,
title: '纭鍒犻櫎',
content: `纭畾瑕佸垹闄ゆ姤琛?${report.name}"鍚楋紵`,
success: (res) => {
if (res.confirm) {
doDeleteReport(report)
@@ -321,11 +321,11 @@ async function doDeleteReport(report: CustomReport) {
await ensureSupabaseReady()
await deleteCustomReport(report.id)
uni.showToast({ title: '删除成功', icon: 'success' })
uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
loadReports()
} catch (e: any) {
console.error('doDeleteReport failed', e)
const errorMsg = e?.message || '删除失败'
const errorMsg = e?.message || '鍒犻櫎澶辫触'
uni.showToast({ title: errorMsg, icon: 'none' })
}
}
@@ -345,9 +345,9 @@ function toggleMetric(key: string) {
function onNameInput() {
const name = reportForm.name.trim()
if (name.length === 0) {
formErrors.name = '报表名称不能为空'
formErrors.name = '鎶ヨ〃鍚嶇О涓嶈兘涓虹┖'
} else if (name.length > 50) {
formErrors.name = '报表名称不能超过50个字符'
formErrors.name = '鎶ヨ〃鍚嶇О涓嶈兘瓒呰繃50涓瓧绗?
} else {
formErrors.name = ''
}
@@ -356,7 +356,7 @@ function onNameInput() {
function onDescriptionInput() {
const desc = reportForm.description
if (desc.length > 200) {
formErrors.description = '报表描述不能超过200个字符'
formErrors.description = '鎶ヨ〃鎻忚堪涓嶈兘瓒呰繃200涓瓧绗?
} else {
formErrors.description = ''
}
@@ -377,20 +377,20 @@ function validateReportForm(): boolean {
onDescriptionInput()
if (reportForm.metrics.length === 0) {
formErrors.metrics = '请至少选择一个指标'
formErrors.metrics = '璇疯嚦灏戦€夋嫨涓€涓寚鏍?
} else {
formErrors.metrics = ''
}
if (!reportForm.period) {
formErrors.period = '请选择时间维度'
formErrors.period = '璇烽€夋嫨鏃堕棿缁村害'
}
if (!reportForm.chartType) {
formErrors.chartType = '请选择图表类型'
formErrors.chartType = '璇烽€夋嫨鍥捐〃绫诲瀷'
}
if (formErrors.name || formErrors.description || formErrors.metrics || formErrors.period || formErrors.chartType) {
uni.showToast({ title: '请先修正表单中的错误提示', icon: 'none' })
uni.showToast({ title: '璇峰厛淇琛ㄥ崟涓殑閿欒鎻愮ず', icon: 'none' })
return false
}
return true
@@ -402,15 +402,15 @@ async function saveReport() {
}
try {
uni.showLoading({ title: '保存中...' })
uni.showLoading({ title: '淇濆瓨涓?..' })
await ensureSupabaseReady()
const uid = getUserIdOrNull()
if (!uid || uid.length === 0) {
uni.hideLoading()
uni.showModal({
title: '需要登录',
content: '创建自定义报表需要先登录,是否前往登录页面?',
title: '闇€瑕佺櫥褰?,
content: '鍒涘缓鑷畾涔夋姤琛ㄩ渶瑕佸厛鐧诲綍锛屾槸鍚﹀墠寰€鐧诲綍椤甸潰锛?,
success: (res) => {
if (res.confirm) {
goToLogin('/pages/mall/analytics/custom-report')
@@ -442,17 +442,17 @@ async function saveReport() {
uni.hideLoading()
// 检查 newReportId 是否有效,无效则认为创建失败
// 妫€鏌?newReportId 鏄惁鏈夋晥锛屾棤鏁堝垯璁や负鍒涘缓澶辫触
if (newReportId == null || newReportId.length === 0) {
uni.showToast({
title: '创建失败:未返回报表ID',
title: '鍒涘缓澶辫触锛氭湭杩斿洖鎶ヨ〃ID',
icon: 'none',
duration: 3000
})
return
}
uni.showToast({ title: '保存成功', icon: 'success' })
uni.showToast({ title: '淇濆瓨鎴愬姛', icon: 'success' })
closeModal()
loadReports()
@@ -466,7 +466,7 @@ async function saveReport() {
uni.hideLoading()
console.error('saveReport exception:', e)
uni.showToast({
title: mapAnalyticsError(e, { fallbackMessage: '保存失败' }),
title: mapAnalyticsError(e, { fallbackMessage: '淇濆瓨澶辫触' }),
icon: 'none',
duration: 3000
})
@@ -486,7 +486,7 @@ function closeModal() {
function refreshData() {
loadReports()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function handleMenu() {
@@ -506,31 +506,31 @@ function closeMoreMenu() {
}
function goToLogin() {
ensureAnalyticsLogin({ toastTitle: '请先登录后使用自定义报表' })
ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庝娇鐢ㄨ嚜瀹氫箟鎶ヨ〃' })
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
function handleGoToLogin() {
@@ -545,7 +545,7 @@ function handleGoToLogin() {
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -557,18 +557,18 @@ function handleGoToLogin() {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
/* 椤堕儴鏍?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -701,7 +701,7 @@ function handleGoToLogin() {
color: #111;
}
/* 工具栏 */
/* 宸ュ叿鏍?*/
.toolbar {
margin-top: 12px;
padding: 12px 16px;
@@ -752,7 +752,7 @@ function handleGoToLogin() {
opacity: 0.9;
}
/* 报表列表 */
/* 鎶ヨ〃鍒楄〃 */
.report-list {
margin-top: 12px;
display: flex;
@@ -858,7 +858,7 @@ function handleGoToLogin() {
text-align: center;
}
/* 模态框 */
/* 妯℃€佹 */
.modal {
position: fixed;
top: 0;
@@ -1009,7 +1009,7 @@ function handleGoToLogin() {
color: #fff;
}
/* 响应式 */
/* 鍝嶅簲寮?*/
@media screen and (max-width: 960px) {
.title,
.subtitle {
@@ -1030,7 +1030,7 @@ function handleGoToLogin() {
}
}
/* 响应式:窄屏时全屏显示 */
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
@@ -1041,3 +1041,4 @@ function handleGoToLogin() {
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'配送效率分析'"
:title="'閰嶉€佹晥鐜囧垎鏋?"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,18 +16,18 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 时间维度筛选(快捷 + 自定义) -->
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -43,8 +43,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
<AnalyticsDateRangePicker
@@ -55,53 +54,53 @@
@clear="onDateRangeClear"
/>
<!-- KPI 指标卡片 -->
<!-- KPI 鎸囨爣鍗$墖 -->
<view class="kpi-grid">
<view class="kpi-card">
<text class="kpi-label">配送时效</text>
<text class="kpi-value">{{ deliveryData.avg_delivery_time }}分钟</text>
<text class="kpi-meta">较上期:{{ formatPct(deliveryData.time_growth) }}</text>
<text class="kpi-label">閰嶉€佹椂鏁?/text>
<text class="kpi-value">{{ deliveryData.avg_delivery_time }}鍒嗛挓</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(deliveryData.time_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">配送费用</text>
<text class="kpi-value">¥{{ formatMoney(deliveryData.total_fee) }}</text>
<text class="kpi-meta">平均:¥{{ formatMoney(deliveryData.avg_fee) }}</text>
<text class="kpi-label">閰嶉€佽垂鐢?/text>
<text class="kpi-value">{{ formatMoney(deliveryData.total_fee) }}</text>
<text class="kpi-meta">骞冲潎锛毬{ formatMoney(deliveryData.avg_fee) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">配送员效率</text>
<text class="kpi-label">閰嶉€佸憳鏁堢巼</text>
<text class="kpi-value">{{ formatInt(deliveryData.avg_orders_per_driver) }}</text>
<text class="kpi-meta">单/人/天</text>
<text class="kpi-meta">鍗?浜?澶?/text>
</view>
<view class="kpi-card">
<text class="kpi-label">客户满意度</text>
<text class="kpi-value">{{ formatScore(deliveryData.satisfaction_rate) }}分</text>
<text class="kpi-meta">较上期:{{ formatPct(deliveryData.satisfaction_growth) }}</text>
<text class="kpi-label">瀹㈡埛婊℃剰搴?/text>
<text class="kpi-value">{{ formatScore(deliveryData.satisfaction_rate) }}鍒?/text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(deliveryData.satisfaction_growth) }}</text>
</view>
</view>
<!-- 配送时效 & 满意度(合并图表) -->
<!-- 閰嶉€佹椂鏁?& 婊℃剰搴︼紙鍚堝苟鍥捐〃锛?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">配送时效 & 满意度</text>
<text class="card-desc">{{ selectedPeriodText }} · 平均配送时间 / 满意度趋势</text>
<text class="card-title">閰嶉€佹椂鏁?& 婊℃剰搴?/text>
<text class="card-desc">{{ selectedPeriodText }} 路 骞冲潎閰嶉€佹椂闂?/ 婊℃剰搴﹁秼鍔?/text>
</view>
<EChartsView class="chart-box" :option="timeChartOption" />
</view>
<!-- 配送费用分析 -->
<!-- 閰嶉€佽垂鐢ㄥ垎鏋?-->
<view class="card">
<view class="card-head">
<text class="card-title">配送费用分析</text>
<text class="card-desc">费用分布情况</text>
<text class="card-title">閰嶉€佽垂鐢ㄥ垎鏋?/text>
<text class="card-desc">璐圭敤鍒嗗竷鎯呭喌</text>
</view>
<EChartsView class="chart-box" :option="feeChartOption" />
</view>
<!-- 配送员效率排行 -->
<!-- 閰嶉€佸憳鏁堢巼鎺掕 -->
<view class="card">
<view class="card-head">
<text class="card-title">配送员效率排行 TOP 10</text>
<text class="card-desc">按订单数排序</text>
<text class="card-title">閰嶉€佸憳鏁堢巼鎺掕 TOP 10</text>
<text class="card-desc">鎸夎鍗曟暟鎺掑簭</text>
</view>
<view class="rank-scroll" @mouseenter="onRankHover(true)" @mouseleave="onRankHover(false)">
<scroll-view class="rank-scroll-inner" :scroll-y="true" :show-scrollbar="true">
@@ -110,9 +109,9 @@
<text class="rank-no">{{ d.rank }}</text>
<text class="rank-name">{{ d.name }}</text>
<view class="rank-right">
<text class="rank-val">{{ d.orders }} 单</text>
<text class="rank-val">{{ d.orders }} 鍗?/text>
<text class="chip" :class="d.rating >= 4.5 ? 'pos' : 'neg'">
⭐{{ d.rating }}
猸恵{ d.rating }}
</text>
</view>
</view>
@@ -121,7 +120,7 @@
</view>
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -156,10 +155,10 @@ const showSidebarMenu = ref(false)
const currentPath = ref('/pages/mall/analytics/delivery-analysis')
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const deliveryData = reactive<DeliveryData>({
@@ -182,7 +181,7 @@ const _trendRows = ref<Array<UTSJSONObject>>([])
const selectedPeriodText = computed((): string => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
onLoad(() => {
@@ -269,7 +268,7 @@ async function loadDeliveryData() {
list.push({
id: r.getString('driver_id') ?? String(i),
rank: i + 1,
name: r.getString('driver_name') ?? '未知',
name: r.getString('driver_name') ?? '鏈煡',
orders: r.getNumber('orders') ?? 0,
rating: r.getNumber('rating_avg') ?? 0
})
@@ -282,7 +281,7 @@ async function loadDeliveryData() {
console.error('loadDeliveryData failed:', e)
updateTime()
buildChartOptions()
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '配送分析数据加载失败' }), icon: 'none' })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '閰嶉€佸垎鏋愭暟鎹姞杞藉け璐? }), icon: 'none' })
}
}
@@ -314,13 +313,13 @@ function onDateRangeClear() {
function refreshData() {
loadDeliveryData()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
@@ -333,13 +332,13 @@ function updateTime() {
function formatInt(n: number): string {
const v = isFinite(n) ? Math.round(n) : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toString()
}
function formatMoney(n: number): string {
const v = isFinite(n) ? n : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toFixed(2)
}
@@ -374,7 +373,7 @@ function buildChartOptions() {
timeChartOption.value = {
tooltip: { trigger: 'axis' },
legend: {
data: ['平均配送时间(分钟)', '满意度(评分)'],
data: ['骞冲潎閰嶉€佹椂闂?鍒嗛挓)', '婊℃剰搴?璇勫垎)'],
top: 'bottom',
itemGap: 30,
itemWidth: 16,
@@ -389,13 +388,13 @@ function buildChartOptions() {
yAxis: [
{
type: 'value',
name: '配送时间',
name: '閰嶉€佹椂闂?,
min: 0,
splitLine: { lineStyle: { color: '#e5e7eb' } }
},
{
type: 'value',
name: '满意度',
name: '婊℃剰搴?,
min: 0,
max: 5,
position: 'right',
@@ -404,7 +403,7 @@ function buildChartOptions() {
],
series: [
{
name: '平均配送时间(分钟)',
name: '骞冲潎閰嶉€佹椂闂?鍒嗛挓)',
type: 'line',
smooth: true,
symbolSize: 6,
@@ -412,7 +411,7 @@ function buildChartOptions() {
yAxisIndex: 0
},
{
name: '满意度(评分)',
name: '婊℃剰搴?璇勫垎)',
type: 'line',
smooth: true,
symbol: 'circle',
@@ -430,7 +429,7 @@ function buildChartOptions() {
yAxis: { type: 'value' },
series: [
{
name: '平均配送费(元)',
name: '骞冲潎閰嶉€佽垂(鍏?',
type: 'bar',
data: feeSeries
}
@@ -455,27 +454,27 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
function onRankHover(hover: boolean) {
@@ -489,7 +488,7 @@ function onRankHover(hover: boolean) {
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -501,18 +500,18 @@ function onRankHover(hover: boolean) {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
/* 椤堕儴鏍?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -645,7 +644,7 @@ function onRankHover(hover: boolean) {
color: #111;
}
/* 时间维度 tabs */
/* 鏃堕棿缁村害 tabs */
.tabs {
margin-top: 12px;
display: flex;
@@ -675,7 +674,7 @@ function onRankHover(hover: boolean) {
color: #fff;
}
/* KPI 网格 */
/* KPI 缃戞牸 */
.kpi-grid {
margin-top: 12px;
display: flex;
@@ -712,7 +711,7 @@ function onRankHover(hover: boolean) {
color: rgba(0,0,0,0.55);
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -874,3 +873,4 @@ function onRankHover(hover: boolean) {
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click.self="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'数据分析中心'"
:title="'鏁版嵁鍒嗘瀽涓績'"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,25 +16,25 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- KPI:宽屏 4列窄屏 2列增强版渐变背景 + sparkline -->
<!-- KPI锛氬灞?4鍒楋紝绐勫睆 2鍒楋紙澧炲己鐗堬細娓愬彉鑳屾櫙 + sparkline锛?-->
<view class="kpi-grid">
<view class="kpi-card kpi-card-gmv" @click="goToSalesReport">
<view class="kpi-header">
<text class="kpi-label">实时 GMV</text>
<text class="kpi-label">瀹炴椂 GMV</text>
</view>
<text class="kpi-value">¥{{ formatMoney(realTime.gmv) }}</text>
<text class="kpi-value">{{ formatMoney(realTime.gmv) }}</text>
<view class="kpi-footer">
<text class="kpi-meta">较昨日同刻</text>
<text class="kpi-meta">杈冩槰鏃ュ悓鍒?/text>
<text class="kpi-chip" :class="realTime.gmv_growth >= 0 ? 'pos' : 'neg'">
{{ formatPct(realTime.gmv_growth) }}
</text>
@@ -42,11 +42,11 @@
</view>
<view class="kpi-card kpi-card-orders" @click="goToSalesReport">
<view class="kpi-header">
<text class="kpi-label">实时订单</text>
<text class="kpi-label">瀹炴椂璁㈠崟</text>
</view>
<text class="kpi-value">{{ formatInt(realTime.orders) }}</text>
<view class="kpi-footer">
<text class="kpi-meta">较昨日同刻</text>
<text class="kpi-meta">杈冩槰鏃ュ悓鍒?/text>
<text class="kpi-chip" :class="realTime.order_growth >= 0 ? 'pos' : 'neg'">
{{ formatPct(realTime.order_growth) }}
</text>
@@ -54,21 +54,21 @@
</view>
<view class="kpi-card kpi-card-users" @click="goToUserAnalysis">
<view class="kpi-header">
<text class="kpi-label">在线用户</text>
<text class="kpi-label">鍦ㄧ嚎鐢ㄦ埛</text>
</view>
<text class="kpi-value">{{ formatInt(realTime.online_users) }}</text>
<view class="kpi-footer">
<text class="kpi-meta">最近 5 分钟</text>
<text class="kpi-chip neutral">实时</text>
<text class="kpi-meta">鏈€杩?5 鍒嗛挓</text>
<text class="kpi-chip neutral">瀹炴椂</text>
</view>
</view>
<view class="kpi-card kpi-card-conversion" @click="goToSalesReport">
<view class="kpi-header">
<text class="kpi-label">转化率</text>
<text class="kpi-label">杞寲鐜?/text>
</view>
<text class="kpi-value">{{ formatPct(realTime.conversion_rate) }}</text>
<view class="kpi-footer">
<text class="kpi-meta">较昨日同刻</text>
<text class="kpi-meta">杈冩槰鏃ュ悓鍒?/text>
<text class="kpi-chip" :class="realTime.conversion_growth >= 0 ? 'pos' : 'neg'">
{{ formatPct(realTime.conversion_growth) }}
</text>
@@ -76,7 +76,7 @@
</view>
</view>
<!-- 时间维度筛选(快捷 + 自定义) -->
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -92,8 +92,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
<AnalyticsDateRangePicker
@@ -104,15 +103,15 @@
@clear="onDateRangeClear"
/>
<!-- 核心趋势:占满横向(柱+折 组合图) -->
<!-- 鏍稿績瓒嬪娍锛氬崰婊℃í鍚戯紙鏌?鎶?缁勫悎鍥撅級 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">核心趋势GMV / 订单数)</text>
<text class="card-desc">{{ selectedPeriodText }} · 柱GMV · 线:订单数</text>
<text class="card-title">鏍稿績瓒嬪娍锛圙MV / 璁㈠崟鏁帮級</text>
<text class="card-desc">{{ selectedPeriodText }} 路 鏌憋細GMV锛堝厓锛?路 绾匡細璁㈠崟鏁?/text>
</view>
<view v-if="loading || !trend.x || trend.x.length === 0" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<view v-else style="width: 100%; height: 320px; position: relative; overflow: hidden; min-height: 320px;">
<AnalyticsComboChart
@@ -125,16 +124,16 @@
</view>
</view>
<!-- 用户结构和流量来源:横排显示 -->
<!-- 鐢ㄦ埛缁撴瀯鍜屾祦閲忔潵婧愶細妯帓鏄剧ず -->
<view class="charts-row">
<!-- 左侧:用户结构 -->
<!-- 宸︿晶锛氱敤鎴风粨鏋?-->
<view class="charts-left card">
<view class="card-head">
<text class="card-title">用户结构(环形图)</text>
<text class="card-desc">未消费 / 首购 / 复购 / 回流</text>
<text class="card-title">鐢ㄦ埛缁撴瀯锛堢幆褰㈠浘锛?/text>
<text class="card-desc">鏈秷璐?/ 棣栬喘 / 澶嶈喘 / 鍥炴祦</text>
</view>
<view v-if="loading || !userSegmentOption || !userSegmentOption.series || (userSegmentOption.series && userSegmentOption.series.length === 0)" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<view v-else class="chart-box">
<EChartsView
@@ -144,14 +143,14 @@
</view>
</view>
<!-- 右侧:流量来源 -->
<!-- 鍙充晶锛氭祦閲忔潵婧?-->
<view class="charts-right card">
<view class="card-head">
<text class="card-title">流量来源(条形)</text>
<text class="card-desc">占比%</text>
<text class="card-title">娴侀噺鏉ユ簮锛堟潯褰級</text>
<text class="card-desc">鍗犳瘮%</text>
</view>
<view v-if="loading || !trafficBarOption || !trafficBarOption.series || trafficBarOption.series.length === 0" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<view v-else class="chart-box">
<EChartsView
@@ -162,13 +161,13 @@
</view>
</view>
<!-- 两个TOP排行横排显示 -->
<!-- 涓や釜TOP鎺掕锛氭í鎺掓樉绀?-->
<view class="tops-row">
<!-- 左侧:热销商品TOP -->
<!-- 宸︿晶锛氱儹閿€鍟嗗搧TOP -->
<view class="tops-left card">
<view class="card-head">
<text class="card-title">热销商品 TOP</text>
<text class="card-desc">按销量</text>
<text class="card-title">鐑攢鍟嗗搧 TOP</text>
<text class="card-desc">鎸夐攢閲?/text>
</view>
<view class="rank-scroll-container">
<view class="rank-scroll-wrapper" :class="{ 'has-scroll': topProducts.length >= 6 }">
@@ -176,24 +175,24 @@
<view v-for="p in topProducts" :key="p.id" class="rank-item">
<text class="rank-no">{{ p.rank }}</text>
<text class="rank-name">{{ p.name }}</text>
<text class="rank-val">{{ p.sales }} 件</text>
<text class="rank-val">{{ p.sales }} 浠?/text>
</view>
<!-- 循环播放:复制一份数据用于无缝滚动 -->
<!-- 寰幆鎾斁锛氬鍒朵竴浠芥暟鎹敤浜庢棤缂濇粴鍔?-->
<view v-if="topProducts.length >= 6" v-for="p in topProducts" :key="'copy-' + p.id" class="rank-item">
<text class="rank-no">{{ p.rank }}</text>
<text class="rank-name">{{ p.name }}</text>
<text class="rank-val">{{ p.sales }} 件</text>
<text class="rank-val">{{ p.sales }} 浠?/text>
</view>
</view>
</view>
</view>
</view>
<!-- 右侧商家排行TOP -->
<!-- 鍙充晶锛氬晢瀹舵帓琛孴OP -->
<view class="tops-right card">
<view class="card-head">
<text class="card-title">商家排行 TOP</text>
<text class="card-desc">GMV</text>
<text class="card-title">鍟嗗鎺掕 TOP</text>
<text class="card-desc">鎸?GMV</text>
</view>
<view class="rank-scroll-container">
<view class="rank-scroll-wrapper" :class="{ 'has-scroll': topMerchants.length >= 6 }">
@@ -202,18 +201,18 @@
<text class="rank-no">{{ m.rank }}</text>
<text class="rank-name">{{ m.name }}</text>
<view class="rank-right">
<text class="rank-val">¥{{ formatMoney(m.sales) }}</text>
<text class="rank-val">{{ formatMoney(m.sales) }}</text>
<text class="chip" :class="m.growth >= 0 ? 'pos' : 'neg'">
{{ m.growth >= 0 ? '+' : '' }}{{ m.growth }}%
</text>
</view>
</view>
<!-- 循环播放:复制一份数据用于无缝滚动 -->
<!-- 寰幆鎾斁锛氬鍒朵竴浠芥暟鎹敤浜庢棤缂濇粴鍔?-->
<view v-if="topMerchants.length >= 6" v-for="m in topMerchants" :key="'copy-' + m.id" class="rank-item">
<text class="rank-no">{{ m.rank }}</text>
<text class="rank-name">{{ m.name }}</text>
<view class="rank-right">
<text class="rank-val">¥{{ formatMoney(m.sales) }}</text>
<text class="rank-val">{{ formatMoney(m.sales) }}</text>
<text class="chip" :class="m.growth >= 0 ? 'pos' : 'neg'">
{{ m.growth >= 0 ? '+' : '' }}{{ m.growth }}%
</text>
@@ -225,47 +224,47 @@
</view>
</view>
<!-- 快速工具卡片区6个工具入口 -->
<!-- 蹇€熷伐鍏峰崱鐗囧尯锛?涓伐鍏峰叆鍙o級 -->
<view class="tools-section">
<view class="section-header">
<text class="section-title">快速分析工具</text>
<text class="section-desc">点击进入详细分析</text>
<text class="section-title">蹇€熷垎鏋愬伐鍏?/text>
<text class="section-desc">鐐瑰嚮杩涘叆璇︾粏鍒嗘瀽</text>
</view>
<view class="tools-grid">
<view class="tool-card" @click="goToSalesReport">
<view class="tool-icon sales">📊</view>
<text class="tool-title">销售报表</text>
<text class="tool-desc">GMV、订单、转化率</text>
<view class="tool-icon sales">馃搳</view>
<text class="tool-title">閿€鍞姤琛?/text>
<text class="tool-desc">GMV銆佽鍗曘€佽浆鍖栫巼</text>
</view>
<view class="tool-card" @click="goToUserAnalysis">
<view class="tool-icon users">👥</view>
<text class="tool-title">用户分析</text>
<text class="tool-desc">增长、活跃、留存</text>
<view class="tool-icon users">馃懃</view>
<text class="tool-title">鐢ㄦ埛鍒嗘瀽</text>
<text class="tool-desc">澧為暱銆佹椿璺冦€佺暀瀛?/text>
</view>
<view class="tool-card" @click="goToProductInsights">
<view class="tool-icon products">📦</view>
<text class="tool-title">商品洞察</text>
<text class="tool-desc">销量、库存、价格</text>
<view class="tool-icon products">馃摝</view>
<text class="tool-title">鍟嗗搧娲炲療</text>
<text class="tool-desc">閿€閲忋€佸簱瀛樸€佷环鏍?/text>
</view>
<view class="tool-card" @click="goToMarketTrends">
<view class="tool-icon market">📈</view>
<text class="tool-title">市场趋势</text>
<text class="tool-desc">整体趋势、行业对比</text>
<view class="tool-icon market">馃搱</view>
<text class="tool-title">甯傚満瓒嬪娍</text>
<text class="tool-desc">鏁翠綋瓒嬪娍銆佽涓氬姣?/text>
</view>
<view class="tool-card" @click="goToCouponAnalysis">
<view class="tool-icon coupon">🎫</view>
<text class="tool-title">优惠券分析</text>
<text class="tool-desc">发放、使用、ROI</text>
<view class="tool-icon coupon">馃帿</view>
<text class="tool-title">浼樻儬鍒稿垎鏋?/text>
<text class="tool-desc">鍙戞斁銆佷娇鐢ㄣ€丷OI</text>
</view>
<view class="tool-card" @click="goToCustomReport">
<view class="tool-icon custom">⚙️</view>
<text class="tool-title">自定义报表</text>
<text class="tool-desc">创建专属报表</text>
<view class="tool-icon custom">鈿欙笍</view>
<text class="tool-title">鑷畾涔夋姤琛?/text>
<text class="tool-desc">鍒涘缓涓撳睘鎶ヨ〃</text>
</view>
</view>
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -302,10 +301,10 @@ const autoRefreshInterval = ref(60000)
const autoRefreshTimer = ref<any>(null)
const timePeriods = ref<Array<{ value: string; label: string }>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const realTime = reactive({
@@ -329,7 +328,7 @@ const userSegmentOption = ref<any>({})
const selectedPeriodText = computed((): string => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
function updateTime() {
@@ -364,7 +363,7 @@ async function loadTrend() {
trend.gmv = data.gmv
trend.orders = data.orders
} catch (e) {
console.error('loadTrend failed', e)
console.error('鉂?loadTrend failed', e)
trend.x = []
trend.gmv = []
trend.orders = []
@@ -382,7 +381,7 @@ async function loadRealTime() {
realTime.conversion_rate = data.conversion_rate
realTime.conversion_growth = data.conversion_growth
} catch (e) {
console.error('loadRealTime failed', e)
console.error('鉂?loadRealTime failed', e)
}
}
@@ -394,7 +393,7 @@ async function loadTopProducts() {
const list = await fetchDashboardTopProducts(selectedPeriod.value, 50, range)
topProducts.splice(0, topProducts.length, ...list)
} catch (e) {
console.error('loadTopProducts failed', e)
console.error('鉂?loadTopProducts failed', e)
topProducts.splice(0, topProducts.length)
}
}
@@ -407,7 +406,7 @@ async function loadTopMerchants() {
const list = await fetchDashboardTopMerchants(selectedPeriod.value, 50, range)
topMerchants.splice(0, topMerchants.length, ...list)
} catch (e) {
console.error('loadTopMerchants failed', e)
console.error('鉂?loadTopMerchants failed', e)
topMerchants.splice(0, topMerchants.length)
}
}
@@ -420,7 +419,7 @@ async function loadUserSegments() {
const list = await fetchDashboardUserSegments(selectedPeriod.value, range)
userSegments.splice(0, userSegments.length, ...list)
} catch (e) {
console.error('loadUserSegments failed', e)
console.error('鉂?loadUserSegments failed', e)
userSegments.splice(0, userSegments.length)
}
}
@@ -433,7 +432,7 @@ async function loadTrafficSources() {
const list = await fetchDashboardTrafficSources(selectedPeriod.value, range)
trafficSources.splice(0, trafficSources.length, ...list)
} catch (e) {
console.error('loadTrafficSources failed', e)
console.error('鉂?loadTrafficSources failed', e)
trafficSources.splice(0, trafficSources.length)
}
}
@@ -469,12 +468,12 @@ function toPlainObject(obj: any): any {
}
function buildChartOptions() {
console.log('📊 buildChartOptions: 开始构建图表配置')
console.log('📊 buildChartOptions: trafficSources', trafficSources, '数量:', trafficSources.length)
console.log('📊 buildChartOptions: userSegments', userSegments, '数量:', userSegments.length)
console.log('馃搳 buildChartOptions: 寮€濮嬫瀯寤哄浘琛ㄩ厤缃?)
console.log('馃搳 buildChartOptions: trafficSources', trafficSources, '鏁伴噺:', trafficSources.length)
console.log('馃搳 buildChartOptions: userSegments', userSegments, '鏁伴噺:', userSegments.length)
if (!trafficSources || !userSegments) {
console.warn('⚠️ buildChartOptions: 数据未准备好,跳过构建')
console.warn('鈿狅笍 buildChartOptions: 鏁版嵁鏈噯澶囧ソ锛岃烦杩囨瀯寤?)
return
}
@@ -485,15 +484,15 @@ function buildChartOptions() {
})
const total = trafficY.reduce((sum, v) => sum + v, 0)
console.log('📊 buildChartOptions: 流量来源数据', { trafficX, trafficY, total, count: trafficX.length })
console.log('馃搳 buildChartOptions: 娴侀噺鏉ユ簮鏁版嵁', { trafficX, trafficY, total, count: trafficX.length })
if (trafficX.length === 0 || total === 0) {
console.warn('⚠️ buildChartOptions: 流量来源数据为空,使用占位数据')
console.warn('鈿狅笍 buildChartOptions: 娴侀噺鏉ユ簮鏁版嵁涓虹┖锛屼娇鐢ㄥ崰浣嶆暟鎹?)
trafficBarOption.value = toPlainObject({
grid: { left: 80, right: 24, top: 18, bottom: 18 },
tooltip: { trigger: 'axis' },
xAxis: { type: 'value', axisLabel: { color: 'rgba(0,0,0,0.55)' } },
yAxis: { type: 'category', data: ['暂无数据'], axisTick: { show: false } },
yAxis: { type: 'category', data: ['鏆傛棤鏁版嵁'], axisTick: { show: false } },
series: [{ type: 'bar', data: [0], barWidth: 14 }]
})
} else {
@@ -505,7 +504,7 @@ function buildChartOptions() {
formatter: (params: any) => {
const p = params[0]
const percent = total > 0 ? ((p.value / total) * 100).toFixed(1) : '0'
return `${p.name}<br/>${p.marker} ${p.value} (${percent}%)`
return `${p.name}<br/>${p.marker} ${p.value} 娆?(${percent}%)`
}
},
xAxis: {
@@ -547,7 +546,7 @@ function buildChartOptions() {
trafficBarOption.value = toPlainObject(newTrafficOption)
}
console.log('📊 buildChartOptions: trafficBarOption 构建完成', trafficBarOption.value)
console.log('馃搳 buildChartOptions: trafficBarOption 鏋勫缓瀹屾垚', trafficBarOption.value)
const segmentData = userSegments.map((it) => ({
name: String(it.name),
@@ -557,10 +556,10 @@ function buildChartOptions() {
})()
}))
console.log('📊 buildChartOptions: 用户结构数据', segmentData, '数量:', segmentData.length)
console.log('馃搳 buildChartOptions: 鐢ㄦ埛缁撴瀯鏁版嵁', segmentData, '鏁伴噺:', segmentData.length)
if (segmentData.length === 0) {
console.warn('⚠️ buildChartOptions: 用户结构数据为空,使用占位数据')
console.warn('鈿狅笍 buildChartOptions: 鐢ㄦ埛缁撴瀯鏁版嵁涓虹┖锛屼娇鐢ㄥ崰浣嶆暟鎹?)
userSegmentOption.value = toPlainObject({
tooltip: { trigger: 'item' },
legend: { left: 0, bottom: 0, itemWidth: 10, itemHeight: 10, textStyle: { fontSize: 12 } },
@@ -570,7 +569,7 @@ function buildChartOptions() {
type: 'pie',
radius: ['55%', '75%'],
center: ['50%', '45%'],
data: [{ name: '暂无数据', value: 1 }],
data: [{ name: '鏆傛棤鏁版嵁', value: 1 }],
label: { show: true, formatter: '{b}\n{d}%' }
}
]
@@ -610,14 +609,14 @@ function buildChartOptions() {
userSegmentOption.value = toPlainObject(newUserSegmentOption)
}
console.log('📊 buildChartOptions: userSegmentOption 构建完成', userSegmentOption.value)
console.log('📊 buildChartOptions: 图表配置构建完成')
console.log('馃搳 buildChartOptions: userSegmentOption 鏋勫缓瀹屾垚', userSegmentOption.value)
console.log('馃搳 buildChartOptions: 鍥捐〃閰嶇疆鏋勫缓瀹屾垚')
}
watch(
trafficSources,
(newVal) => {
console.log('👀 watch trafficSources 触发', newVal)
console.log('馃憖 watch trafficSources 瑙﹀彂', newVal)
if (newVal && newVal.length > 0) {
buildChartOptions()
}
@@ -628,7 +627,7 @@ watch(
watch(
userSegments,
(newVal) => {
console.log('👀 watch userSegments 触发', newVal)
console.log('馃憖 watch userSegments 瑙﹀彂', newVal)
if (newVal && newVal.length > 0) {
buildChartOptions()
}
@@ -649,26 +648,26 @@ async function refreshAll() {
])
updateTime()
console.log('refreshAll: 所有数据加载完成,开始构建图表')
console.log('鉁?refreshAll: 鎵€鏈夋暟鎹姞杞藉畬鎴愶紝寮€濮嬫瀯寤哄浘琛?)
await new Promise((resolve) => {
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(() => {
buildChartOptions()
console.log('refreshAll: 图表配置构建完成')
console.log('鉁?refreshAll: 鍥捐〃閰嶇疆鏋勫缓瀹屾垚')
resolve(null)
})
} else {
setTimeout(() => {
buildChartOptions()
console.log('refreshAll: 图表配置构建完成')
console.log('鉁?refreshAll: 鍥捐〃閰嶇疆鏋勫缓瀹屾垚')
resolve(null)
}, 50)
}
})
} catch (e) {
console.error('refreshAll failed', e)
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '数据加载失败' }), icon: 'none' })
console.error('鉂?refreshAll failed', e)
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '鏁版嵁鍔犺浇澶辫触' }), icon: 'none' })
}
}
@@ -687,7 +686,7 @@ async function initDashboard() {
async function selectPeriod(p: string) {
selectedPeriod.value = p
// 切换到快捷时间段时,退出自定义范围
// 鍒囨崲鍒板揩鎹锋椂闂存鏃讹紝閫€鍑鸿嚜瀹氫箟鑼冨洿
customRangeEnabled.value = false
selectedStartDate.value = ''
selectedEndDate.value = ''
@@ -700,19 +699,19 @@ async function selectPeriod(p: string) {
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(() => {
buildChartOptions()
console.log('selectPeriod: 图表配置构建完成')
console.log('鉁?selectPeriod: 鍥捐〃閰嶇疆鏋勫缓瀹屾垚')
resolve(null)
})
} else {
setTimeout(() => {
buildChartOptions()
console.log('selectPeriod: 图表配置构建完成')
console.log('鉁?selectPeriod: 鍥捐〃閰嶇疆鏋勫缓瀹屾垚')
resolve(null)
}, 50)
}
})
} catch (e) {
console.error('selectPeriod failed', e)
console.error('鉂?selectPeriod failed', e)
} finally {
loading.value = false
}
@@ -731,30 +730,30 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索功能', icon: 'none' })
uni.showToast({ title: '鎼滅储鍔熻兘', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知中心', icon: 'none' })
uni.showToast({ title: '閫氱煡涓績', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏模式', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆妯″紡', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端预览', icon: 'none' })
uni.showToast({ title: '绉诲姩绔瑙?, icon: 'none' })
}
function handleDropdown() {
uni.showActionSheet({
itemList: ['crmeb demo', '切换项目', '项目设置'],
itemList: ['crmeb demo', '鍒囨崲椤圭洰', '椤圭洰璁剧疆'],
success: () => {}
})
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
function toggleCustomRange() {
@@ -777,13 +776,13 @@ function onDateRangeClear() {
function formatInt(n: number): string {
const v = isFinite(n) ? Math.round(n) : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toString()
}
function formatMoney(n: number): string {
const v = isFinite(n) ? n : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toFixed(0)
}
@@ -845,22 +844,22 @@ function toggleAutoRefresh() {
autoRefreshEnabled.value = !autoRefreshEnabled.value
if (autoRefreshEnabled.value) {
startAutoRefresh()
uni.showToast({ title: '已开启自动刷新', icon: 'success' })
uni.showToast({ title: '宸插紑鍚嚜鍔ㄥ埛鏂?, icon: 'success' })
} else {
stopAutoRefresh()
uni.showToast({ title: '已关闭自动刷新', icon: 'none' })
uni.showToast({ title: '宸插叧闂嚜鍔ㄥ埛鏂?, icon: 'none' })
}
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
onLoad(() => {
if (!ensureAnalyticsLogin({ toastTitle: '请先登录后查看数据分析' })) return
if (!ensureAnalyticsLogin({ toastTitle: '璇峰厛鐧诲綍鍚庢煡鐪嬫暟鎹垎鏋? })) return
initDashboard()
})
@@ -881,15 +880,15 @@ onHide(() => {
</script>
<style>
/* 页面:白底 + 宽屏居中 + 自适应 */
/* 说明uni-app rpx 会随屏宽缩放,宽屏 H5 建议用 max-width 控制内容宽度。 */
/* 椤甸潰锛氱櫧搴?+ 瀹藉睆灞呬腑 + 鑷€傚簲 */
/* 璇存槑锛歶ni-app 鐨?rpx 浼氶殢灞忓缂╂斁锛屽灞?H5 寤鸿鐢?max-width 鎺у埗鍐呭瀹藉害銆?*/
.page {
min-height: 100vh;
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -901,19 +900,19 @@ onHide(() => {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
flex: 1;
}
/* 响应式:窄屏时全屏显示 */
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
@@ -924,8 +923,8 @@ onHide(() => {
}
}
/* 顶部 */
/* ✅ 强制:顶部必须横排(避免被全局 view:flex-direction:column 影响) */
/* 椤堕儴 */
/* 鉁?寮哄埗锛氶《閮ㄥ繀椤绘í鎺掞紙閬垮厤琚叏灞€ view:flex-direction:column 褰卞搷锛?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -973,13 +972,13 @@ onHide(() => {
line-height: 1;
}
/* 左侧标题组仍然是纵向 */
/* 宸︿晶鏍囬缁勪粛鐒舵槸绾靛悜 */
.title-group {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
min-width: 0; /* 允许内部 text 做省略 */
min-width: 0; /* 鍏佽鍐呴儴 text 鍋氱渷鐣?*/
}
.title {
@@ -1001,7 +1000,7 @@ onHide(() => {
white-space: nowrap;
}
/* ✅ 右侧按钮永不换成竖列(必要时只裁切,不换行) */
/* 鉁?鍙充晶鎸夐挳姘镐笉鎹㈡垚绔栧垪锛堝繀瑕佹椂鍙鍒囷紝涓嶆崲琛岋級 */
.topbar-right {
display: flex;
flex-direction: row !important;
@@ -1026,7 +1025,7 @@ onHide(() => {
color: #fff;
}
/* 图标按钮样式 */
/* 鍥炬爣鎸夐挳鏍峰紡 */
.icon-btn-icon {
width: 32px;
height: 32px;
@@ -1050,7 +1049,7 @@ onHide(() => {
line-height: 1;
}
/* 通知图标带红点 */
/* 閫氱煡鍥炬爣甯︾孩鐐?*/
.icon-btn-icon.notification .badge {
position: absolute;
top: 4px;
@@ -1062,7 +1061,7 @@ onHide(() => {
border: 1px solid #fff;
}
/* 下拉菜单 */
/* 涓嬫媺鑿滃崟 */
.dropdown {
display: flex;
flex-direction: row !important;
@@ -1077,7 +1076,7 @@ onHide(() => {
flex-shrink: 0;
}
/* 更多按钮(默认隐藏,窄屏时显示) */
/* 鏇村鎸夐挳锛堥粯璁ら殣钘忥紝绐勫睆鏃舵樉绀猴級 */
.more-btn {
display: none;
width: 32px;
@@ -1102,7 +1101,7 @@ onHide(() => {
color: #111;
}
/* 更多菜单下拉 */
/* 鏇村鑿滃崟涓嬫媺 */
.more-menu {
position: absolute;
top: calc(100% + 8px);
@@ -1157,8 +1156,8 @@ onHide(() => {
line-height: 1;
}
/* KPI:默认 2列宽屏 4列 */
/* ✅ 核心修复:用 flex + calc(50%) 替代 width,避免 rpx + CSS var 失效 */
/* KPI锛氶粯璁?2鍒楋紝瀹藉睆 4鍒?*/
/* 鉁?鏍稿績淇锛氱敤 flex + calc(50%) 鏇夸唬 width锛岄伩鍏?rpx + CSS var 澶辨晥 */
.kpi-grid {
margin-top: 12px;
display: flex;
@@ -1187,7 +1186,7 @@ onHide(() => {
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
}
/* KPI 卡片渐变背景 */
/* KPI 鍗$墖娓愬彉鑳屾櫙 */
.kpi-card-gmv {
background: linear-gradient(135deg, #FF6B6B 0%, #FF4D4F 100%);
color: #fff;
@@ -1269,7 +1268,7 @@ onHide(() => {
color: #fff;
}
/* 时间维度 tabs 横排 */
/* 鏃堕棿缁村害 tabs 妯帓 */
.tabs {
margin-top: 12px;
display: flex;
@@ -1299,7 +1298,7 @@ onHide(() => {
color: #fff;
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -1336,17 +1335,17 @@ onHide(() => {
color: rgba(0,0,0,0.55);
}
/* 图表必须给高度H5 否则可能 0 高) */
/* 鍥捐〃蹇呴』缁欓珮搴︼紙H5 鍚﹀垯鍙兘 0 楂橈級 */
.chart-box {
width: 100%;
height: 360px; /* 建议用 pxH5 更稳 */
height: 360px; /* 寤鸿鐢?px锛孒5 鏇寸ǔ */
}
.fullwide .chart-box {
height: 420px; /* 大图更高 */
height: 420px; /* 澶у浘鏇撮珮 */
}
/* 图表加载状态 */
/* 鍥捐〃鍔犺浇鐘舵€?*/
.chart-loading {
width: 100%;
height: 360px;
@@ -1361,7 +1360,7 @@ onHide(() => {
height: 420px;
}
/* 用户结构和流量来源:横排显示 */
/* 鐢ㄦ埛缁撴瀯鍜屾祦閲忔潵婧愶細妯帓鏄剧ず */
.charts-row {
display: flex;
flex-direction: row !important;
@@ -1377,7 +1376,7 @@ onHide(() => {
min-width: 360px;
}
/* 两个TOP排行横排显示 */
/* 涓や釜TOP鎺掕锛氭í鎺掓樉绀?*/
.tops-row {
display: flex;
flex-direction: row !important;
@@ -1393,7 +1392,7 @@ onHide(() => {
min-width: 360px;
}
/* 滚动容器 */
/* 婊氬姩瀹瑰櫒 */
.rank-scroll-container {
width: 100%;
height: 300px;
@@ -1409,7 +1408,7 @@ onHide(() => {
animation: scrollRank 15s linear infinite;
}
/* 列表样式 */
/* 鍒楄〃鏍峰紡 */
.rank-list {
display: flex;
flex-direction: column;
@@ -1417,14 +1416,14 @@ onHide(() => {
width: 100%;
}
/* 滚动动画当数据超过5条时自动滚动 */
/* 婊氬姩鍔ㄧ敾锛氬綋鏁版嵁瓒呰繃5鏉℃椂鑷姩婊氬姩 */
@keyframes scrollRank {
0% {
transform: translateY(0);
}
100% {
/* 滚动到第一份数据的末尾,实现无缝循环 */
/* 每条 rank-item 高度约 50px包括 padding gap),滚动一半高度 */
/* 婊氬姩鍒扮涓€浠芥暟鎹殑鏈熬锛屽疄鐜版棤缂濆惊鐜?*/
/* 姣忔潯 rank-item 楂樺害绾?50px锛堝寘鎷?padding 鍜?gap锛夛紝婊氬姩涓€鍗婇珮搴?*/
transform: translateY(calc(-50%));
}
}
@@ -1487,14 +1486,14 @@ onHide(() => {
color: #dc2626;
}
/* 宽屏KPI 4*/
/* 瀹藉睆锛欿PI 4鍒?*/
@media screen and (min-width: 960px) {
.kpi-card {
flex: 1 1 calc(25% - 9px);
min-width: 200px;
}
/* 宽屏时显示所有按钮,隐藏"更多"按钮 */
/* 瀹藉睆鏃舵樉绀烘墍鏈夋寜閽紝闅愯棌"鏇村"鎸夐挳 */
.topbar-right .btn-hidden {
display: flex !important;
}
@@ -1503,13 +1502,13 @@ onHide(() => {
display: none !important;
}
/* 宽屏时工具卡片 3列 */
/* 瀹藉睆鏃跺伐鍏峰崱鐗?3鍒?*/
.tools-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* 自适应:窄屏自动变一列(断点用 px */
/* 鑷€傚簲锛氱獎灞忚嚜鍔ㄥ彉涓€鍒楋紙鏂偣鐢?px锛?*/
@media screen and (max-width: 960px) {
.charts-row,
.tops-row {
@@ -1529,22 +1528,22 @@ onHide(() => {
height: 360px;
}
/* 顶部栏按钮在小屏幕上:隐藏部分按钮,显示"更多"按钮 */
/* 椤堕儴鏍忔寜閽湪灏忓睆骞曚笂锛氶殣钘忛儴鍒嗘寜閽紝鏄剧ず"鏇村"鎸夐挳 */
.topbar-right {
gap: 6px;
}
/* 隐藏标记为 btn-hidden 的按钮 */
/* 闅愯棌鏍囪涓?btn-hidden 鐨勬寜閽?*/
.topbar-right .btn-hidden {
display: none !important;
}
/* 显示"更多"按钮 */
/* 鏄剧ず"鏇村"鎸夐挳 */
.more-btn {
display: flex !important;
}
/* 标题在窄屏时允许省略号 */
/* 鏍囬鍦ㄧ獎灞忔椂鍏佽鐪佺暐鍙?*/
.title,
.subtitle {
max-width: 200px;
@@ -1569,18 +1568,18 @@ onHide(() => {
font-size: 12px;
}
/* 窄屏时 KPI 卡片单列 */
/* 绐勫睆鏃?KPI 鍗$墖鍗曞垪 */
.kpi-card {
flex: 1 1 100%;
}
/* 窄屏时工具卡片 2列 */
/* 绐勫睆鏃跺伐鍏峰崱鐗?2鍒?*/
.tools-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 快速工具卡片区 */
/* 蹇€熷伐鍏峰崱鐗囧尯 */
.tools-section {
margin-top: 24px;
}
@@ -1680,3 +1679,4 @@ onHide(() => {
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<template>
<view class="page" @click="closeMoreMenu">
<AnalyticsTopBar
:title="'数据洞察详情'"
:title="'鏁版嵁娲炲療璇︽儏'"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -25,7 +25,7 @@
<view class="container">
<view class="card card-full">
<view class="card-head">
<text class="card-title">{{ insight.title || '洞察详情' }}</text>
<text class="card-title">{{ insight.title || '娲炲療璇︽儏' }}</text>
<view class="meta-row">
<text class="badge" :class="'badge-' + (insight.type || 'info')">{{ getInsightTypeText(insight.type) }}</text>
<text class="badge badge-impact" :class="'impact-' + (insight.impact || 'medium')">{{ getImpactText(insight.impact) }}</text>
@@ -34,7 +34,7 @@
</view>
<view v-if="loading" class="state">
<text class="state-text">加载中...</text>
<text class="state-text">鍔犺浇涓?..</text>
</view>
<view v-else-if="errorMsg" class="state">
<text class="state-text">{{ errorMsg }}</text>
@@ -46,11 +46,11 @@
<view class="card" v-if="relatedReport.id">
<view class="card-head">
<text class="card-title">关联报表</text>
<text class="card-desc">{{ relatedReport.type }} · {{ relatedReport.period }}</text>
<text class="card-title">鍏宠仈鎶ヨ〃</text>
<text class="card-desc">{{ relatedReport.type }} {{ relatedReport.period }}</text>
</view>
<view class="report-row" @click="goToReportDetail">
<view class="report-icon">📄</view>
<view class="report-icon">馃搫</view>
<view class="report-info">
<text class="report-title">{{ relatedReport.title }}</text>
<text class="report-time">{{ relatedReport.generated_at ? formatTime(relatedReport.generated_at) : '' }}</text>
@@ -110,7 +110,7 @@ onLoad((options: any) => {
const iid = (options.insightId || options.id) as string
if (!iid) {
uni.showToast({ title: '缺少洞察ID', icon: 'none' })
uni.showToast({ title: '缂哄皯娲炲療ID', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
return
}
@@ -131,7 +131,7 @@ async function loadInsightDetail() {
const data = await fetchInsightDetail(insightId.value)
if (data == null) {
errorMsg.value = '洞察不存在或无权限访问'
errorMsg.value = '娲炲療涓嶅瓨鍦ㄦ垨鏃犳潈闄愯闂?
return
}
@@ -165,7 +165,7 @@ async function loadInsightDetail() {
}
} catch (e) {
console.error('loadInsightDetail failed', e)
errorMsg.value = mapAnalyticsError(e, { fallbackMessage: '加载失败,请稍后重试' })
errorMsg.value = mapAnalyticsError(e, { fallbackMessage: '鍔犺浇澶辫触锛岃绋嶅悗閲嶈瘯' })
} finally {
loading.value = false
}
@@ -173,13 +173,13 @@ async function loadInsightDetail() {
function refreshData() {
void loadInsightDetail()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
@@ -198,21 +198,21 @@ function formatTime(timeStr: string): string {
function getInsightTypeText(type: string): string {
const t = `${type || 'info'}`
const map: Record<string, string> = {
positive: '正向',
warning: '预警',
negative: '风险',
info: '信息'
positive: '姝e悜',
warning: '棰勮',
negative: '椋庨櫓',
info: '淇℃伅'
}
return map[t] || '信息'
return map[t] || '淇℃伅'
}
function getImpactText(impact: string): string {
const impacts: Record<string, string> = {
high: '高影响',
medium: '中影响',
low: '低影响'
high: '楂樺奖鍝?,
medium: '涓奖鍝?,
low: '浣庡奖鍝?
}
return impacts[impact || 'medium'] || '中影响'
return impacts[impact || 'medium'] || '涓奖鍝?
}
function goToReportDetail() {
@@ -239,27 +239,27 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
</script>
@@ -287,7 +287,8 @@ function handleSettings() {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click.self="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'市场趋势'"
:title="'甯傚満瓒嬪娍'"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,18 +16,18 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 时间维度筛选(快捷 + 自定义) -->
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -43,8 +43,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
<AnalyticsDateRangePicker
@@ -55,52 +54,52 @@
@clear="onDateRangeClear"
/>
<!-- 市场整体趋势 -->
<!-- 甯傚満鏁翠綋瓒嬪娍 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">市场整体趋势</text>
<text class="card-desc">{{ selectedPeriodText }} · GMV、订单数、用户数</text>
<text class="card-title">甯傚満鏁翠綋瓒嬪娍</text>
<text class="card-desc">{{ selectedPeriodText }} GMV銆佽鍗曟暟銆佺敤鎴锋暟</text>
</view>
<EChartsView class="chart-box" :option="marketTrendOption" />
</view>
<!-- 行业对比分析 -->
<!-- 琛屼笟瀵规瘮鍒嗘瀽 -->
<view class="card">
<view class="card-head">
<text class="card-title">行业对比分析</text>
<text class="card-desc">不同行业表现对比</text>
<text class="card-title">琛屼笟瀵规瘮鍒嗘瀽</text>
<text class="card-desc">涓嶅悓琛屼笟琛ㄧ幇瀵规瘮</text>
</view>
<EChartsView class="chart-box" :option="industryCompareOption" />
</view>
<!-- 季节性趋势 -->
<!-- 瀛h妭鎬ц秼鍔?-->
<view class="card">
<view class="card-head">
<text class="card-title">季节性趋势</text>
<text class="card-desc">按月份统计</text>
<text class="card-title">瀛h妭鎬ц秼鍔?/text>
<text class="card-desc">鎸夋湀浠界粺璁?/text>
</view>
<EChartsView class="chart-box" :option="seasonalTrendOption" />
</view>
<!-- 价格趋势分析 -->
<!-- 浠锋牸瓒嬪娍鍒嗘瀽 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">价格趋势分析</text>
<text class="card-desc">平均价格变化趋势</text>
<text class="card-title">浠锋牸瓒嬪娍鍒嗘瀽</text>
<text class="card-desc">骞冲潎浠锋牸鍙樺寲瓒嬪娍</text>
</view>
<EChartsView class="chart-box" :option="priceTrendOption" />
</view>
<!-- 竞争分析 -->
<!-- 绔炰簤鍒嗘瀽 -->
<view class="card">
<view class="card-head">
<text class="card-title">竞争分析</text>
<text class="card-desc">市场份额、增长率对比</text>
<text class="card-title">绔炰簤鍒嗘瀽</text>
<text class="card-desc">甯傚満浠介銆佸闀跨巼瀵规瘮</text>
</view>
<EChartsView class="chart-box" :option="competitionOption" />
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -135,10 +134,10 @@ const showSidebarMenu = ref(false)
const currentPath = ref('/pages/mall/analytics/market-trends')
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const marketTrendOption = ref<any>({})
@@ -155,7 +154,7 @@ const _competitionRows = ref<any>(null)
const selectedPeriodText = computed((): string => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
onLoad(() => {
@@ -188,7 +187,7 @@ async function loadMarketData() {
console.error('loadMarketData failed:', e)
updateTime()
buildChartOptions()
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '市场趋势数据加载失败' }), icon: 'none' })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '甯傚満瓒嬪娍鏁版嵁鍔犺浇澶辫触' }), icon: 'none' })
}
}
@@ -202,7 +201,7 @@ function selectPeriod(p: string) {
function refreshData() {
loadMarketData()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function toggleCustomRange() {
@@ -225,8 +224,8 @@ function onDateRangeClear() {
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
@@ -250,8 +249,7 @@ function buildChartOptions() {
const priceRows = Array.isArray(priceAny) ? (priceAny as Array<UTSJSONObject>) : []
const compRows = Array.isArray(compAny) ? (compAny as Array<UTSJSONObject>) : []
// 1) 市场整体趋势GMV / 订单数 / 用户数
const mtDays: string[] = []
// 1) 甯傚満鏁翠綋瓒嬪娍锛欸MV / 璁㈠崟鏁?/ 鐢ㄦ埛鏁? const mtDays: string[] = []
const mtGmv: number[] = []
const mtOrders: number[] = []
const mtUsers: number[] = []
@@ -268,14 +266,14 @@ function buildChartOptions() {
marketTrendOption.value = {
tooltip: { trigger: 'axis' },
legend: {
data: ['GMV', '订单数', '用户数'],
data: ['GMV', '璁㈠崟鏁?, '鐢ㄦ埛鏁?],
top: 'bottom'
},
grid: { left: 50, right: 60, top: 40, bottom: 60 },
xAxis: { type: 'category', data: mtDays },
yAxis: [
{ type: 'value', name: 'GMV', splitLine: { lineStyle: { color: '#e5e7eb' } } },
{ type: 'value', name: '数量', position: 'right', splitLine: { show: false } }
{ type: 'value', name: '鏁伴噺', position: 'right', splitLine: { show: false } }
],
series: [
{
@@ -286,14 +284,14 @@ function buildChartOptions() {
itemStyle: { color: '#3b82f6' }
},
{
name: '订单数',
name: '璁㈠崟鏁?,
type: 'line',
yAxisIndex: 1,
smooth: true,
data: mtOrders
},
{
name: '用户数',
name: '鐢ㄦ埛鏁?,
type: 'line',
yAxisIndex: 1,
smooth: true,
@@ -302,12 +300,12 @@ function buildChartOptions() {
]
}
// 2) 行业对比:分类 GMV
// 2) 琛屼笟瀵规瘮锛氬垎绫?GMV
const catNames: string[] = []
const catSales: number[] = []
for (let i = 0; i < industryRows.length; i++) {
const r = industryRows[i]
catNames.push(r.getString('category_name') ?? '未分类')
catNames.push(r.getString('category_name') ?? '鏈垎绫?)
catSales.push(r.getNumber('total_sales') ?? 0)
}
@@ -325,7 +323,7 @@ function buildChartOptions() {
]
}
// 3) 季节性趋势:按月 GMV
// 3) 瀛h妭鎬ц秼鍔匡細鎸夋湀 GMV
const seaMonths: string[] = []
const seaGmv: number[] = []
for (let i = 0; i < seasonalRows.length; i++) {
@@ -349,8 +347,7 @@ function buildChartOptions() {
]
}
// 4) 价格趋势:按天平均价格
const priceDays: string[] = []
// 4) 浠锋牸瓒嬪娍锛氭寜澶╁钩鍧囦环鏍? const priceDays: string[] = []
const avgPrices: number[] = []
for (let i = 0; i < priceRows.length; i++) {
const r = priceRows[i]
@@ -363,10 +360,10 @@ function buildChartOptions() {
tooltip: { trigger: 'axis' },
grid: { left: 50, right: 20, top: 30, bottom: 60 },
xAxis: { type: 'category', data: priceDays },
yAxis: { type: 'value', name: '平均价格' },
yAxis: { type: 'value', name: '骞冲潎浠锋牸' },
series: [
{
name: '平均价格',
name: '骞冲潎浠锋牸',
type: 'line',
smooth: true,
data: avgPrices
@@ -374,12 +371,12 @@ function buildChartOptions() {
]
}
// 5) 竞争分析:商家 GMV 对比
// 5) 绔炰簤鍒嗘瀽锛氬晢瀹?GMV 瀵规瘮
const merchantNames: string[] = []
const merchantGmv: number[] = []
for (let i = 0; i < compRows.length; i++) {
const r = compRows[i]
merchantNames.push(r.getString('merchant_name') ?? '未知商家')
merchantNames.push(r.getString('merchant_name') ?? '鏈煡鍟嗗')
merchantGmv.push(r.getNumber('gmv') ?? 0)
}
@@ -388,7 +385,7 @@ function buildChartOptions() {
legend: { top: 'bottom' },
series: [
{
name: '商家GMV',
name: '鍟嗗GMV',
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '50%'],
@@ -417,27 +414,27 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
</script>
@@ -447,7 +444,7 @@ function handleSettings() {
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -459,18 +456,18 @@ function handleSettings() {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
/* 椤堕儴鏍?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -603,7 +600,7 @@ function handleSettings() {
color: #111;
}
/* 时间维度 tabs */
/* 鏃堕棿缁村害 tabs */
.tabs {
margin-top: 12px;
display: flex;
@@ -633,7 +630,7 @@ function handleSettings() {
color: #fff;
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -687,7 +684,7 @@ function handleSettings() {
}
}
/* 响应式:窄屏时全屏显示 */
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
@@ -698,3 +695,4 @@ function handleSettings() {
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click.self="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'商品洞察'"
:title="'鍟嗗搧娲炲療'"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,18 +16,18 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 时间维度筛选(快捷 + 自定义) -->
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -43,8 +43,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
<AnalyticsDateRangePicker
@@ -55,34 +54,34 @@
@clear="onDateRangeClear"
/>
<!-- KPI 指标卡片 -->
<!-- KPI 鎸囨爣鍗$墖 -->
<view class="kpi-grid">
<view class="kpi-card">
<text class="kpi-label">商品总数</text>
<text class="kpi-label">鍟嗗搧鎬绘暟</text>
<text class="kpi-value">{{ formatInt(productData.total_products) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(productData.product_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(productData.product_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">热销商品</text>
<text class="kpi-label">鐑攢鍟嗗搧</text>
<text class="kpi-value">{{ formatInt(productData.hot_products) }}</text>
<text class="kpi-meta">销量 &gt; 100</text>
<text class="kpi-meta">閿€閲?&gt; 100</text>
</view>
<view class="kpi-card">
<text class="kpi-label">库存周转率</text>
<text class="kpi-label">搴撳瓨鍛ㄨ浆鐜?/text>
<text class="kpi-value">{{ formatPct(productData.turnover_rate) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(productData.turnover_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(productData.turnover_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">平均库存</text>
<text class="kpi-label">骞冲潎搴撳瓨</text>
<text class="kpi-value">{{ formatInt(productData.avg_stock) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(productData.stock_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(productData.stock_growth) }}</text>
</view>
</view>
<!-- 商品销售分析 -->
<!-- 鍟嗗搧閿€鍞垎鏋?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">商品销售分析</text>
<text class="card-title">鍟嗗搧閿€鍞垎鏋?/text>
<view class="card-head-right">
<select class="select" v-model="selectedProductId" @change="handleProductChange">
<option v-for="p in topProducts" :key="p.id" :value="p.id">{{ p.name }}</option>
@@ -90,33 +89,33 @@
</view>
</view>
<view v-if="loading || !salesChartOption || !salesChartOption.series || salesChartOption.series.length === 0" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<EChartsView v-else class="chart-box" :option="salesChartOption" />
</view>
<!-- 第二行:分类 & 排行 -->
<!-- 绗簩琛岋細鍒嗙被 & 鎺掕 -->
<view class="grid-row">
<!-- 商品分类分析 -->
<!-- 鍟嗗搧鍒嗙被鍒嗘瀽 -->
<view class="card grid-col-item">
<view class="card-head">
<text class="card-title">商品分类分析</text>
<text class="card-desc">按分类统计销售额</text>
<text class="card-title">鍟嗗搧鍒嗙被鍒嗘瀽</text>
<text class="card-desc">鎸夊垎绫荤粺璁¢攢鍞</text>
</view>
<view v-if="loading || !categoryChartOption || !categoryChartOption.series || categoryChartOption.series.length === 0" class="chart-loading chart-loading-sm">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<EChartsView v-else class="chart-box chart-box-sm" :option="categoryChartOption" />
</view>
<!-- 热销商品排行 -->
<!-- 鐑攢鍟嗗搧鎺掕 -->
<view class="card grid-col-item">
<view class="card-head">
<text class="card-title">热销商品排行 TOP 10</text>
<text class="card-desc">按销量排序</text>
<text class="card-title">鐑攢鍟嗗搧鎺掕 TOP 10</text>
<text class="card-desc">鎸夐攢閲忔帓搴?/text>
</view>
<view v-if="loading || topProducts.length === 0" class="chart-loading chart-loading-sm">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<view v-else class="rank-list-scroll">
<view class="rank-list">
@@ -124,7 +123,7 @@
<text class="rank-no">{{ p.rank }}</text>
<text class="rank-name">{{ p.name }}</text>
<view class="rank-right">
<text class="rank-val">{{ p.sales }} 件</text>
<text class="rank-val">{{ p.sales }} 浠?/text>
<text class="chip" :class="p.growth >= 0 ? 'pos' : 'neg'">
{{ p.growth >= 0 ? '+' : '' }}{{ p.growth }}%
</text>
@@ -135,46 +134,46 @@
</view>
</view>
<!-- 第三行:库存 & 价格 -->
<!-- 绗笁琛岋細搴撳瓨 & 浠锋牸 -->
<view class="grid-row">
<!-- 商品库存分析 -->
<!-- 鍟嗗搧搴撳瓨鍒嗘瀽 -->
<view class="card grid-col-item">
<view class="card-head">
<text class="card-title">商品库存分析</text>
<text class="card-desc">库存分布情况</text>
<text class="card-title">鍟嗗搧搴撳瓨鍒嗘瀽</text>
<text class="card-desc">搴撳瓨鍒嗗竷鎯呭喌</text>
</view>
<view v-if="loading || !stockChartOption || !stockChartOption.series || stockChartOption.series.length === 0" class="chart-loading chart-loading-sm">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<EChartsView v-else class="chart-box chart-box-sm" :option="stockChartOption" />
</view>
<!-- 商品价格趋势 -->
<!-- 鍟嗗搧浠锋牸瓒嬪娍 -->
<view class="card grid-col-item">
<view class="card-head">
<text class="card-title">商品价格趋势</text>
<text class="card-desc">平均价格变化</text>
<text class="card-title">鍟嗗搧浠锋牸瓒嬪娍</text>
<text class="card-desc">骞冲潎浠锋牸鍙樺寲</text>
</view>
<view v-if="loading || !priceChartOption || !priceChartOption.series || priceChartOption.series.length === 0" class="chart-loading chart-loading-sm">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<EChartsView v-else class="chart-box chart-box-sm" :option="priceChartOption" />
</view>
</view>
<!-- 第四行:评价 -->
<!-- 绗洓琛岋細璇勪环 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">商品评价分析</text>
<text class="card-desc">评分分布</text>
<text class="card-title">鍟嗗搧璇勪环鍒嗘瀽</text>
<text class="card-desc">璇勫垎鍒嗗竷</text>
</view>
<view v-if="loading || !reviewChartOption || !reviewChartOption.series || reviewChartOption.series.length === 0" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<EChartsView v-else class="chart-box" :option="reviewChartOption" />
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -208,10 +207,10 @@ const showSidebarMenu = ref(false)
const currentPath = ref('/pages/mall/analytics/product-insights')
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const productData = reactive<ProductData>({
@@ -237,7 +236,7 @@ const loading = ref(false)
const selectedPeriodText = computed((): string => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
onLoad(() => {
@@ -254,7 +253,7 @@ function updateTime() {
function formatInt(n: number): string {
const v = isFinite(n) ? Math.round(n) : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toString()
}
@@ -295,16 +294,16 @@ async function loadSelectedProductTrend() {
salesChartOption.value = {
grid: { left: 50, right: 50, top: 20, bottom: 46 },
tooltip: { trigger: 'axis' },
legend: { data: ['GMV', '件数', '订单数'], bottom: 0 },
legend: { data: ['GMV', '浠舵暟', '璁㈠崟鏁?], bottom: 0 },
xAxis: { type: 'category', data: x, axisLabel: { color: 'rgba(0,0,0,0.55)' } },
yAxis: [
{ type: 'value', name: 'GMV', axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } } },
{ type: 'value', name: '件/单', axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { show: false } }
{ type: 'value', name: '浠?鍗?, axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { show: false } }
],
series: [
{ name: 'GMV', type: 'bar', data: gmv, barWidth: 14, itemStyle: { borderRadius: 6 } },
{ name: '件数', type: 'line', yAxisIndex: 1, data: qty, smooth: true, symbolSize: 6 },
{ name: '订单数', type: 'line', yAxisIndex: 1, data: orders, smooth: true, symbolSize: 6 }
{ name: '浠舵暟', type: 'line', yAxisIndex: 1, data: qty, smooth: true, symbolSize: 6 },
{ name: '璁㈠崟鏁?, type: 'line', yAxisIndex: 1, data: orders, smooth: true, symbolSize: 6 }
]
}
@@ -319,13 +318,13 @@ async function loadSelectedProductTrend() {
grid: { left: 40, right: 18, top: 20, bottom: 40 },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: x, axisLabel: { color: 'rgba(0,0,0,0.55)' } },
yAxis: { type: 'value', name: '均价', axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } } },
series: [{ name: '均价', type: 'line', data: avgPrice, smooth: true, symbolSize: 6, color: '#f97316' }]
yAxis: { type: 'value', name: '鍧囦环', axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } } },
series: [{ name: '鍧囦环', type: 'line', data: avgPrice, smooth: true, symbolSize: 6, color: '#f97316' }]
}
} catch (e) {
console.error('loadSelectedProductTrend failed', e)
salesChartOption.value = {}
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '加载商品趋势失败' }), icon: 'none' })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '鍔犺浇鍟嗗搧瓒嬪娍澶辫触' }), icon: 'none' })
}
}
@@ -338,7 +337,7 @@ function buildCategoryChart(catRows: any) {
const names: Array<string> = []
const values: Array<number> = []
for (let i = 0; i < rows.length; i++) {
names.push(`${rows[i].category_name ?? '未分类'}`)
names.push(`${rows[i].category_name ?? '鏈垎绫?}`)
values.push(Number(rows[i].total_sales) || 0)
}
@@ -435,7 +434,7 @@ async function loadProductData() {
updateTime()
} catch (e) {
console.error('loadProductData failed', e)
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '商品洞察数据加载失败' }), icon: 'none', duration: 2000 })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '鍟嗗搧娲炲療鏁版嵁鍔犺浇澶辫触' }), icon: 'none', duration: 2000 })
} finally {
loading.value = false
updateTime()
@@ -470,13 +469,13 @@ function onDateRangeClear() {
function refreshData() {
loadProductData()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
@@ -497,27 +496,27 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
</script>
@@ -527,7 +526,7 @@ function handleSettings() {
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -539,18 +538,18 @@ function handleSettings() {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
/* 椤堕儴鏍?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -684,7 +683,7 @@ function handleSettings() {
color: #111;
}
/* 时间维度 tabs */
/* 鏃堕棿缁村害 tabs */
.tabs {
margin-top: 12px;
display: flex;
@@ -714,7 +713,7 @@ function handleSettings() {
color: #fff;
}
/* KPI 网格 */
/* KPI 缃戞牸 */
.kpi-grid {
margin-top: 12px;
display: flex;
@@ -751,7 +750,7 @@ function handleSettings() {
color: rgba(0,0,0,0.55);
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -929,7 +928,7 @@ function handleSettings() {
}
}
/* 响应式:窄屏时全屏显示 */
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
@@ -940,3 +939,4 @@ function handleSettings() {
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click.self="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'销售报表'"
:title="'閿€鍞姤琛?"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,18 +16,18 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 时间维度筛选(快捷 + 自定义) -->
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -43,8 +43,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
<AnalyticsDateRangePicker
@@ -55,38 +54,38 @@
@clear="onDateRangeClear"
/>
<!-- KPI 指标卡片 -->
<!-- KPI 鎸囨爣鍗$墖 -->
<view class="kpi-grid">
<view class="kpi-card">
<text class="kpi-label">GMV(成交总额)</text>
<text class="kpi-value">¥{{ formatMoney(salesData.gmv) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(salesData.gmv_growth) }}</text>
<text class="kpi-label">GMV锛堟垚浜ゆ€婚锛?/text>
<text class="kpi-value">{{ formatMoney(salesData.gmv) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.gmv_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">订单量</text>
<text class="kpi-label">璁㈠崟閲?/text>
<text class="kpi-value">{{ formatInt(salesData.orders) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(salesData.order_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.order_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">转化率</text>
<text class="kpi-label">杞寲鐜?/text>
<text class="kpi-value">{{ formatPct(salesData.conversion_rate) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(salesData.conversion_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.conversion_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">客单价</text>
<text class="kpi-value">¥{{ formatMoney(salesData.avg_order_amount) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(salesData.avg_order_growth) }}</text>
<text class="kpi-label">瀹㈠崟浠?/text>
<text class="kpi-value">{{ formatMoney(salesData.avg_order_amount) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.avg_order_growth) }}</text>
</view>
</view>
<!-- 销售趋势图表 -->
<!-- 閿€鍞秼鍔垮浘琛?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">销售趋势分析</text>
<text class="card-desc">{{ selectedPeriodText }} · 柱GMV · 线:订单数</text>
<text class="card-title">閿€鍞秼鍔垮垎鏋?/text>
<text class="card-desc">{{ selectedPeriodText }} 路 鏌憋細GMV锛堝厓锛?路 绾匡細璁㈠崟鏁?/text>
</view>
<view v-if="loading || !trend.x || trend.x.length === 0" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<AnalyticsComboChart
v-else
@@ -97,7 +96,7 @@
/>
</view>
<!-- 销售地域分布(左地图 + 右双列表,同一块) -->
<!-- 閿€鍞湴鍩熷垎甯冿紙宸﹀湴鍥?+ 鍙冲弻鍒楄〃锛屽悓涓€鍧楋級 -->
<view class="card card-full sales-overview-card">
<view class="sales-split">
<view class="sales-split-left">
@@ -111,18 +110,18 @@
<view class="sales-split-right">
<view class="sales-split-list">
<view class="list-head">
<text class="list-title">商品销售排行 TOP 10</text>
<text class="list-desc">按销量排序</text>
<text class="list-title">鍟嗗搧閿€鍞帓琛?TOP 10</text>
<text class="list-desc">鎸夐攢閲忔帓搴?/text>
</view>
<view v-if="loading || topProducts.length === 0" class="chart-loading chart-loading-compact">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<view v-else class="rank-scroll">
<view class="rank-list">
<view v-for="p in topProducts" :key="p.id" class="rank-item">
<text class="rank-no">{{ p.rank }}</text>
<text class="rank-name">{{ p.name }}</text>
<text class="rank-val">{{ p.sales }} 件</text>
<text class="rank-val">{{ p.sales }} 浠?/text>
</view>
</view>
</view>
@@ -130,11 +129,11 @@
<view class="sales-split-list">
<view class="list-head">
<text class="list-title">商家销售排行 TOP 10</text>
<text class="list-desc">GMV 排序</text>
<text class="list-title">鍟嗗閿€鍞帓琛?TOP 10</text>
<text class="list-desc">鎸?GMV 鎺掑簭</text>
</view>
<view v-if="loading || topMerchants.length === 0" class="chart-loading chart-loading-compact">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<view v-else class="rank-scroll">
<view class="rank-list">
@@ -142,7 +141,7 @@
<text class="rank-no">{{ m.rank }}</text>
<text class="rank-name">{{ m.name }}</text>
<view class="rank-right">
<text class="rank-val">¥{{ formatMoney(m.sales) }}</text>
<text class="rank-val">{{ formatMoney(m.sales) }}</text>
<text class="chip" :class="m.growth >= 0 ? 'pos' : 'neg'">
{{ m.growth >= 0 ? '+' : '' }}{{ m.growth }}%
</text>
@@ -155,7 +154,7 @@
</view>
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -189,10 +188,10 @@ const currentPath = ref('/pages/mall/analytics/sales-report')
const loading = ref(false)
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const salesData = reactive<SalesData>({
@@ -212,7 +211,7 @@ const topMerchants = reactive<Array<MerchantRank>>([])
const selectedPeriodText = computed((): string => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
onLoad(() => {
@@ -248,13 +247,13 @@ async function loadSalesData() {
salesData.avg_order_amount = kpi.avg_order_amount
salesData.avg_order_growth = kpi.avg_order_growth
// 趋势
// 瓒嬪娍
const t = await fetchSalesTrend(selectedPeriod.value, range)
trend.x = t.x
trend.gmv = t.gmv
trend.orders = t.orders
// TOP 商品/商家
// TOP 鍟嗗搧/鍟嗗
const pList = await fetchSalesTopProducts(selectedPeriod.value, 50, range)
for (let i = 0; i < pList.length; i++) {
pList[i].rank = i + 1
@@ -267,8 +266,8 @@ async function loadSalesData() {
}
topMerchants.splice(0, topMerchants.length, ...mList)
} catch (e) {
console.error('loadSalesData failed', e)
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '数据加载失败' }), icon: 'none', duration: 2000 })
console.error('鉂?loadSalesData failed', e)
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '鏁版嵁鍔犺浇澶辫触' }), icon: 'none', duration: 2000 })
} finally {
loading.value = false
updateTime()
@@ -285,13 +284,13 @@ function selectPeriod(p: string) {
function refreshData() {
loadSalesData()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
@@ -304,13 +303,13 @@ function updateTime() {
function formatInt(n: number): string {
const v = isFinite(n) ? Math.round(n) : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toString()
}
function formatMoney(n: number): string {
const v = isFinite(n) ? n : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toFixed(0)
}
@@ -337,27 +336,27 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
function toggleCustomRange() {
@@ -385,7 +384,7 @@ function onDateRangeClear() {
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -397,18 +396,18 @@ function onDateRangeClear() {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
/* 椤堕儴鏍?*/
.topbar {
display: flex;
flex-direction: row !important;
@@ -542,7 +541,7 @@ function onDateRangeClear() {
color: #111;
}
/* 时间维度 tabs */
/* 鏃堕棿缁村害 tabs */
.tabs {
margin-top: 12px;
display: flex;
@@ -572,7 +571,7 @@ function onDateRangeClear() {
color: #fff;
}
/* KPI 网格 */
/* KPI 缃戞牸 */
.kpi-grid {
margin-top: 12px;
display: flex;
@@ -609,7 +608,7 @@ function onDateRangeClear() {
color: rgba(0,0,0,0.55);
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -729,7 +728,7 @@ function onDateRangeClear() {
}
}
/* 排行列表 */
/* 鎺掕鍒楄〃 */
.rank-scroll {
flex: 1;
min-height: 0;
@@ -738,14 +737,14 @@ function onDateRangeClear() {
padding-right: 6px;
-webkit-overflow-scrolling: touch;
/* 防止滚动链把滚轮事件传给页面 */
/* 闃叉婊氬姩閾炬妸婊氳疆浜嬩欢浼犵粰椤甸潰 */
overscroll-behavior: contain;
scroll-behavior: auto;
/* 默认隐藏滚动条(Firefox */
/* 榛樿闅愯棌婊氬姩鏉★紙Firefox锛?*/
scrollbar-width: none;
/* 默认隐藏滚动条(WebKit */
/* 榛樿闅愯棌婊氬姩鏉★紙WebKit锛?*/
-ms-overflow-style: none;
}
@@ -754,7 +753,7 @@ function onDateRangeClear() {
height: 0;
}
/* 鼠标悬停在方块内时显示滚动条,并允许拖动 */
/* 榧犳爣鎮仠鍦ㄦ柟鍧楀唴鏃舵樉绀烘粴鍔ㄦ潯锛屽苟鍏佽鎷栧姩 */
.sales-split-list:hover .rank-scroll {
scrollbar-width: thin;
scrollbar-color: rgba(0,0,0,0.35) rgba(0,0,0,0.06);
@@ -842,7 +841,7 @@ function onDateRangeClear() {
color: #dc2626;
}
/* 响应式 */
/* 鍝嶅簲寮?*/
@media screen and (min-width: 960px) {
.kpi-card {
flex: 1 1 calc(25% - 9px);
@@ -866,3 +865,4 @@ function onDateRangeClear() {
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<template>
<view class="page" @click.self="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
<AnalyticsTopBar
:title="'用户分析'"
:title="'鐢ㄦ埛鍒嗘瀽'"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@@ -16,21 +16,21 @@
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<!-- 涓诲唴瀹瑰尯鍩?-->
<view class="main-content">
<view class="container">
<!-- 全局筛选区 -->
<!-- 鍏ㄥ眬绛涢€夊尯 -->
<view class="filters">
<view class="filter-block">
<text class="filter-label">时间范围</text>
<text class="filter-label">鏃堕棿鑼冨洿</text>
<view class="tabs">
<view
v-for="p in timePeriods"
@@ -46,8 +46,7 @@
:class="{ active: customRangeEnabled }"
@click="toggleCustomRange"
>
自定义
</view>
鑷畾涔? </view>
</view>
</view>
@@ -60,52 +59,52 @@
/>
<view class="filter-hint">
<text class="filter-hint-text">渠道/终端/会员/新老:待接入数据后开放</text>
<text class="filter-hint-text">娓犻亾/缁堢/浼氬憳/鏂拌€侊細寰呮帴鍏ユ暟鎹悗寮€鏀?/text>
</view>
</view>
<!-- 核心 KPI(电商化) -->
<!-- 鏍稿績 KPI锛堢數鍟嗗寲锛?-->
<view class="kpi-grid kpi-grid-6">
<view class="kpi-card">
<text class="kpi-label">新增用户</text>
<text class="kpi-label">鏂板鐢ㄦ埛</text>
<text class="kpi-value">{{ formatInt(userData.new_users) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(userData.new_user_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(userData.new_user_growth) }}</text>
</view>
<view class="kpi-card">
<text class="kpi-label">活跃用户DAU</text>
<text class="kpi-label">娲昏穬鐢ㄦ埛锛圖AU锛?/text>
<text class="kpi-value">{{ formatInt(userData.active_users) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(userData.active_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(userData.active_growth) }}</text>
</view>
<view class="kpi-card" v-if="hasOrderingData">
<text class="kpi-label">下单用户数</text>
<text class="kpi-label">涓嬪崟鐢ㄦ埛鏁?/text>
<text class="kpi-value">{{ formatInt(userData.ordering_users) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(userData.ordering_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(userData.ordering_growth) }}</text>
</view>
<view class="kpi-card" v-if="hasPaidData">
<text class="kpi-label">支付用户数</text>
<text class="kpi-label">鏀粯鐢ㄦ埛鏁?/text>
<text class="kpi-value">{{ formatInt(userData.paid_users) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(userData.paid_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(userData.paid_growth) }}</text>
</view>
<view class="kpi-card" v-if="hasNewConversionData">
<text class="kpi-label">新客转化率</text>
<text class="kpi-label">鏂板杞寲鐜?/text>
<text class="kpi-value">{{ formatPct(userData.new_user_conversion_rate) }}</text>
<text class="kpi-meta">新客 → 下单/支付</text>
<text class="kpi-meta">鏂板 鈫?涓嬪崟/鏀粯</text>
</view>
<view class="kpi-card">
<text class="kpi-label">复购率</text>
<text class="kpi-label">澶嶈喘鐜?/text>
<text class="kpi-value">{{ formatPct(userData.repurchase_rate) }}</text>
<text class="kpi-meta">较上期:{{ formatPct(userData.repurchase_growth) }}</text>
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(userData.repurchase_growth) }}</text>
</view>
</view>
<!-- 增长与活跃趋势 -->
<!-- 澧為暱涓庢椿璺冭秼鍔?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">增长与活跃趋势</text>
<text class="card-desc">{{ selectedPeriodText }} · 新增 vs 活跃DAU</text>
<text class="card-title">澧為暱涓庢椿璺冭秼鍔?/text>
<text class="card-desc">{{ selectedPeriodText }} 路 鏂板 vs 娲昏穬锛圖AU锛?/text>
</view>
<view v-if="loading || !growthChartOption || !growthChartOption.series || growthChartOption.series.length === 0" class="chart-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
</view>
<EChartsView v-else class="chart-box" :option="growthChartOption" />
</view>
@@ -113,30 +112,30 @@
<view class="two-col">
<view class="card two-col-card">
<view class="card-head">
<text class="card-title">新客转化趋势</text>
<text class="card-desc">新客 → 下单/支付(待接入)</text>
<text class="card-title">鏂板杞寲瓒嬪娍</text>
<text class="card-desc">鏂板 鈫?涓嬪崟/鏀粯锛堝緟鎺ュ叆锛?/text>
</view>
<view class="chart-loading chart-loading-sm">
<text>暂无数据 / 待接入</text>
<text>鏆傛棤鏁版嵁 / 寰呮帴鍏?/text>
</view>
</view>
<view class="card two-col-card">
<view class="card-head">
<text class="card-title">回访 / 复购趋势</text>
<text class="card-desc">复购人数 / 复购率(待接入)</text>
<text class="card-title">鍥炶 / 澶嶈喘瓒嬪娍</text>
<text class="card-desc">澶嶈喘浜烘暟 / 澶嶈喘鐜囷紙寰呮帴鍏ワ級</text>
</view>
<view class="chart-loading chart-loading-sm">
<text>暂无数据 / 待接入</text>
<text>鏆傛棤鏁版嵁 / 寰呮帴鍏?/text>
</view>
</view>
</view>
<!-- 转化漏斗 -->
<!-- 杞寲婕忔枟 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">转化漏斗</text>
<text class="card-desc">拉新 → 激活 → 转化(待接入埋点/事件)</text>
<text class="card-title">杞寲婕忔枟</text>
<text class="card-desc">鎷夋柊 鈫?婵€娲?鈫?杞寲锛堝緟鎺ュ叆鍩嬬偣/浜嬩欢锛?/text>
</view>
<view class="funnel">
<view class="funnel-steps">
@@ -147,49 +146,49 @@
</view>
<view class="funnel-step-metrics">
<text class="funnel-step-value">{{ formatInt(s.value) }}</text>
<text class="funnel-step-rate" v-if="idx > 0">转化:{{ formatPct(calcFunnelRate(idx)) }}</text>
<text class="funnel-step-rate" v-else>—</text>
<text class="funnel-step-rate" v-if="idx > 0">杞寲锛歿{ formatPct(calcFunnelRate(idx)) }}</text>
<text class="funnel-step-rate" v-else>鈥?/text>
</view>
</view>
</view>
<view class="funnel-empty" v-if="!hasFunnelData">
<text class="funnel-empty-text">暂无漏斗数据 / 待接入UV、PDP、加购、下单、支付</text>
<text class="funnel-empty-text">鏆傛棤婕忔枟鏁版嵁 / 寰呮帴鍏ワ細UV銆丳DP銆佸姞璐€佷笅鍗曘€佹敮浠?/text>
</view>
</view>
</view>
<!-- 留存 / 回访 -->
<!-- 鐣欏瓨 / 鍥炶 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">留存与回访</text>
<text class="card-desc">{{ selectedPeriodText }} · 1/3/7/14/30日留存(Cohort 后续补)</text>
<text class="card-title">鐣欏瓨涓庡洖璁?/text>
<text class="card-desc">{{ selectedPeriodText }} 1/3/7/14/30鏃ョ暀瀛橈紙Cohort 鍚庣画琛ワ級</text>
</view>
<view class="two-col">
<view class="two-col-item">
<view class="sub-head">
<text class="sub-title">留存曲线</text>
<text class="sub-desc">留存率趋势(待接入)</text>
<text class="sub-title">鐣欏瓨鏇茬嚎</text>
<text class="sub-desc">鐣欏瓨鐜囪秼鍔匡紙寰呮帴鍏ワ級</text>
</view>
<EChartsView class="chart-box chart-box-sm" :option="retentionChartOption" />
</view>
<view class="two-col-item">
<view class="sub-head">
<text class="sub-title">流失用户占比</text>
<text class="sub-desc">7/14天未活跃(待接入)</text>
<text class="sub-title">娴佸け鐢ㄦ埛鍗犳瘮</text>
<text class="sub-desc">7/14澶╂湭娲昏穬锛堝緟鎺ュ叆锛?/text>
</view>
<view class="metric-empty">
<text class="metric-empty-text">暂无数据 / 待接入</text>
<text class="metric-empty-text">鏆傛棤鏁版嵁 / 寰呮帴鍏?/text>
</view>
</view>
</view>
</view>
<!-- 用户分群(运营可用) -->
<!-- 鐢ㄦ埛鍒嗙兢锛堣繍钀ュ彲鐢級 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">用户分群(运营可用)</text>
<text class="card-desc">RFM / LTV / 新客分层(后续补) · 当前为基础结构占比</text>
<text class="card-title">鐢ㄦ埛鍒嗙兢锛堣繍钀ュ彲鐢級</text>
<text class="card-desc">RFM / LTV / 鏂板鍒嗗眰锛堝悗缁ˉ锛?路 褰撳墠涓哄熀纭€缁撴瀯鍗犳瘮</text>
</view>
<view class="two-col">
<view class="two-col-item">
@@ -197,24 +196,24 @@
</view>
<view class="two-col-item">
<view class="sub-head">
<text class="sub-title">用户画像(基础)</text>
<text class="sub-desc">性别/年龄/地域(待接入)</text>
<text class="sub-title">鐢ㄦ埛鐢诲儚锛堝熀纭€锛?/text>
<text class="sub-desc">鎬у埆/骞撮緞/鍦板煙锛堝緟鎺ュ叆锛?/text>
</view>
<EChartsView class="chart-box chart-box-sm" :option="profileChartOption" />
</view>
</view>
</view>
<!-- 渠道来源Acquisition -->
<!-- 娓犻亾鏉ユ簮锛圓cquisition锛?-->
<view class="card card-full">
<view class="card-head">
<text class="card-title">渠道来源</text>
<text class="card-desc">{{ selectedPeriodText }} · 渠道占比(后续可扩展渠道质量表)</text>
<text class="card-title">娓犻亾鏉ユ簮</text>
<text class="card-desc">{{ selectedPeriodText }} 路 娓犻亾鍗犳瘮锛堝悗缁彲鎵╁睍娓犻亾璐ㄩ噺琛級</text>
</view>
<EChartsView class="chart-box" :option="trafficChartOption" />
</view>
<!-- 留白 -->
<!-- 鐣欑櫧 -->
<view style="height: 24px;"></view>
</view>
</view>
@@ -248,10 +247,10 @@ const showSidebarMenu = ref(false)
const currentPath = ref('/pages/mall/analytics/user-analysis')
const timePeriods = ref<Array<TimePeriod>>([
{ value: '7d', label: '7天' },
{ value: '30d', label: '30天' },
{ value: '90d', label: '90天' },
{ value: '1y', label: '1年' }
{ value: '7d', label: '7澶? },
{ value: '30d', label: '30澶? },
{ value: '90d', label: '90澶? },
{ value: '1y', label: '1骞? }
])
const userData = reactive<UserData>({
@@ -279,18 +278,18 @@ const segmentChartOption = ref<any>({})
const trafficChartOption = ref<any>({})
const funnelSteps = reactive<Array<FunnelStep>>([
{ step: '访问用户UV', value: 0 },
{ step: '商品详情页(PDP UV', value: 0 },
{ step: '加购用户', value: 0 },
{ step: '下单用户', value: 0 },
{ step: '支付用户', value: 0 }
{ step: '璁块棶鐢ㄦ埛锛圲V锛?, value: 0 },
{ step: '鍟嗗搧璇︽儏椤碉紙PDP UV锛?, value: 0 },
{ step: '鍔犺喘鐢ㄦ埛', value: 0 },
{ step: '涓嬪崟鐢ㄦ埛', value: 0 },
{ step: '鏀粯鐢ㄦ埛', value: 0 }
])
const loading = ref(false)
const selectedPeriodText = computed((): string => {
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
return p ? p.label : '7天'
return p ? p.label : '7澶?
})
const hasOrderingData = computed((): boolean => {
@@ -388,12 +387,12 @@ async function loadUserData() {
growthChartOption.value = {
grid: { left: 40, right: 18, top: 20, bottom: 40 },
tooltip: { trigger: 'axis' },
legend: { data: ['新增用户', '活跃用户DAU'], bottom: 0 },
legend: { data: ['鏂板鐢ㄦ埛', '娲昏穬鐢ㄦ埛锛圖AU锛?], bottom: 0 },
xAxis: { type: 'category', data: x, axisLabel: { color: 'rgba(0,0,0,0.55)' } },
yAxis: { type: 'value', axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } } },
series: [
{ name: '新增用户', type: 'line', data: newArr, smooth: true, symbolSize: 6, areaStyle: { opacity: 0.08 } },
{ name: '活跃用户DAU', type: 'line', data: activeArr, smooth: true, symbolSize: 6 }
{ name: '鏂板鐢ㄦ埛', type: 'line', data: newArr, smooth: true, symbolSize: 6, areaStyle: { opacity: 0.08 } },
{ name: '娲昏穬鐢ㄦ埛锛圖AU锛?, type: 'line', data: activeArr, smooth: true, symbolSize: 6 }
]
}
@@ -411,7 +410,7 @@ async function loadUserData() {
legend: { top: 10, left: 'center', padding: [12, 0, 24, 0] },
series: [
{
name: '用户分群',
name: '鐢ㄦ埛鍒嗙兢',
type: 'pie',
center: ['48%', '60%'],
radius: ['42%', '66%'],
@@ -443,20 +442,20 @@ async function loadUserData() {
}
funnelSteps.splice(0, funnelSteps.length,
{ step: '访问用户UV', value: 0 },
{ step: '商品详情页(PDP UV', value: 0 },
{ step: '加购用户', value: 0 },
{ step: '下单用户', value: 0 },
{ step: '支付用户', value: 0 }
{ step: '璁块棶鐢ㄦ埛锛圲V锛?, value: 0 },
{ step: '鍟嗗搧璇︽儏椤碉紙PDP UV锛?, value: 0 },
{ step: '鍔犺喘鐢ㄦ埛', value: 0 },
{ step: '涓嬪崟鐢ㄦ埛', value: 0 },
{ step: '鏀粯鐢ㄦ埛', value: 0 }
)
retentionChartOption.value = { title: { text: '留存率(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
activityChartOption.value = { title: { text: '活跃度(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
comparisonChartOption.value = { title: { text: '新老用户对比(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
profileChartOption.value = { title: { text: '用户画像(待接入:需要性别/年龄/地域字段)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
retentionChartOption.value = { title: { text: '鐣欏瓨鐜囷紙寰呮帴鍏ワ級', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
activityChartOption.value = { title: { text: '娲昏穬搴︼紙寰呮帴鍏ワ級', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
comparisonChartOption.value = { title: { text: '鏂拌€佺敤鎴峰姣旓紙寰呮帴鍏ワ級', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
profileChartOption.value = { title: { text: '鐢ㄦ埛鐢诲儚锛堝緟鎺ュ叆锛氶渶瑕佹€у埆/骞撮緞/鍦板煙瀛楁锛?, left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
} catch (e) {
console.error('loadUserData failed', e)
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '数据加载失败' }), icon: 'none', duration: 2000 })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '鏁版嵁鍔犺浇澶辫触' }), icon: 'none', duration: 2000 })
} finally {
loading.value = false
updateTime()
@@ -491,19 +490,19 @@ function onDateRangeClear() {
function refreshData() {
loadUserData()
uni.showToast({ title: '已刷新', icon: 'success' })
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
})
}
function formatInt(n: number): string {
const v = isFinite(n) ? Math.round(n) : 0
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
return v.toString()
}
@@ -538,27 +537,27 @@ function closeMoreMenu() {
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
uni.showToast({ title: '鎼滅储', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
uni.showToast({ title: '閫氱煡', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
}
function handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
uni.showToast({ title: '璁剧疆', icon: 'none' })
}
</script>
@@ -569,7 +568,7 @@ function handleSettings() {
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
.page-layout {
display: flex;
flex-direction: row !important;
@@ -581,10 +580,10 @@ function handleSettings() {
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
}
/* 响应式:窄屏时全屏显示 */
/* 鍝嶅簲寮忥細绐勫睆鏃跺叏灞忔樉绀?*/
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
@@ -599,11 +598,11 @@ function handleSettings() {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
/* padding removed */ 16px 28px;
box-sizing: border-box;
}
/* 全局筛选区 */
/* 鍏ㄥ眬绛涢€夊尯 */
.filters {
margin-top: 12px;
background: #fff;
@@ -640,7 +639,7 @@ function handleSettings() {
color: #6c757d;
}
/* 时间维度 tabs */
/* 鏃堕棿缁村害 tabs */
.tabs {
display: flex;
flex-direction: row !important;
@@ -671,7 +670,7 @@ function handleSettings() {
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* KPI 网格 */
/* KPI 缃戞牸 */
.kpi-grid {
margin-top: 12px;
display: grid;
@@ -741,7 +740,7 @@ function handleSettings() {
color: rgba(0,0,0,0.55);
}
/* 卡片 */
/* 鍗$墖 */
.card {
margin-top: 12px;
background: #fff;
@@ -856,7 +855,7 @@ function handleSettings() {
}
}
/* 漏斗 */
/* 婕忔枟 */
.funnel {
width: 100%;
}
@@ -950,3 +949,4 @@ function handleSettings() {
}
}
</style>