修改页面结构
This commit is contained in:
345
pages/dashboard/OrderChart.uvue
Normal file
345
pages/dashboard/OrderChart.uvue
Normal file
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<view class="order-chart-container">
|
||||
<view class="chart-header">
|
||||
<view class="header-left">
|
||||
<view class="title-icon">
|
||||
<text class="iconfont">Icon</text>
|
||||
</view>
|
||||
<text class="chart-title">订单分析</text>
|
||||
</view>
|
||||
|
||||
<view class="chart-controls">
|
||||
<view
|
||||
v-for="p in periods"
|
||||
:key="p.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: range === p.value }"
|
||||
@click="changeRange(p.value)"
|
||||
>
|
||||
<text class="seg-btn-text">{{ p.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="chart-body">
|
||||
<!-- 轴标题 -->
|
||||
<view class="axis-titles">
|
||||
<text class="axis-title">金额 (元)</text>
|
||||
<text class="axis-title">数量 (笔)</text>
|
||||
</view>
|
||||
|
||||
<!-- 图表容器 -->
|
||||
<view class="chart-view-wrapper">
|
||||
<EChartsView
|
||||
v-if="!loading && chartOption"
|
||||
:option="chartOption"
|
||||
class="echarts-view"
|
||||
/>
|
||||
|
||||
<!-- Loading 状态 -->
|
||||
<view v-if="loading" class="loading-state">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && stats.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
import { OrderStat } from '@/types/orders.uts'
|
||||
|
||||
// --- Props ---
|
||||
const props = defineProps<{
|
||||
// 可以在这里扩展外部传入的配置
|
||||
}>()
|
||||
|
||||
// --- State ---
|
||||
const range = ref('30d')
|
||||
const loading = ref(false)
|
||||
const stats = ref<OrderStat[]>([])
|
||||
|
||||
const periods = [
|
||||
{ label: '30天', value: '30d' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '年', value: 'year' }
|
||||
]
|
||||
|
||||
// --- ECharts Options ---
|
||||
const chartOption = computed(() => {
|
||||
if (stats.value.length === 0) return null
|
||||
|
||||
const dates = stats.value.map(item => item.date)
|
||||
const amounts = stats.value.map(item => item.totalAmount)
|
||||
const counts = stats.value.map(item => item.orderCount)
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: { color: '#999' }
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '3%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
data: ['订单金额', '订单数'],
|
||||
top: 0,
|
||||
itemWidth: 12,
|
||||
itemHeight: 12
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisPointer: { type: 'shadow' },
|
||||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#999', fontSize: 10 }
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '', // 已经在外部显示标题了
|
||||
splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '',
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '订单金额',
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
data: amounts,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#36a3f7' },
|
||||
{ offset: 1, color: '#a0d4fb' }
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '订单数',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: counts,
|
||||
smooth: true,
|
||||
showSymbol: true,
|
||||
symbolSize: 6,
|
||||
itemStyle: { color: '#2fc25b' },
|
||||
lineStyle: { width: 2, color: '#2fc25b' },
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(47, 194, 91, 0.2)' },
|
||||
{ offset: 1, color: 'rgba(47, 194, 91, 0)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// --- Methods ---
|
||||
|
||||
/**
|
||||
* 内部刷新逻辑,供外部或生命周期调用
|
||||
*/
|
||||
const refresh = async () => {
|
||||
await fetchOrderStats(range.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟接口请求
|
||||
*/
|
||||
const fetchOrderStats = (r: string) => {
|
||||
loading.value = true
|
||||
|
||||
// 模拟网络延迟
|
||||
setTimeout(() => {
|
||||
// 模拟数据生成逻辑
|
||||
const mockData: OrderStat[] = []
|
||||
const count = r === 'week' ? 7 : (r === 'month' ? 30 : (r === 'year' ? 12 : 30))
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
mockData.push({
|
||||
date: r === 'year' ? `${i + 1}月` : `01-${(i + 4).toString().padStart(2, '0')}`,
|
||||
totalAmount: Math.floor(Math.random() * 80000) + (i === 4 ? 60000 : 20000),
|
||||
orderCount: Math.floor(Math.random() * 25) + (i % 3 === 0 ? 8 : 2)
|
||||
})
|
||||
}
|
||||
|
||||
stats.value = mockData
|
||||
loading.value = false
|
||||
// 如果是下拉刷新触发,通知原生停止
|
||||
uni.stopPullDownRefresh()
|
||||
}, 800)
|
||||
}
|
||||
|
||||
const changeRange = (v: string) => {
|
||||
range.value = v
|
||||
}
|
||||
|
||||
// 暴露给父组件的主动刷新方案
|
||||
defineExpose({
|
||||
refresh
|
||||
})
|
||||
|
||||
// --- Lifecycle & Watchers ---
|
||||
onMounted(() => {
|
||||
fetchOrderStats(range.value)
|
||||
})
|
||||
|
||||
watch(range, (newVal) => {
|
||||
fetchOrderStats(newVal)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.order-chart-container {
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f0f0f0;
|
||||
padding: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #e6f7ff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 分段按钮样式 */
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.seg-btn {
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.seg-btn:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.seg-btn.active {
|
||||
background: #1890ff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
.seg-btn-text {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.seg-btn.active .seg-btn-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 图表内容区域 */
|
||||
.chart-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.axis-titles {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.axis-title {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chart-view-wrapper {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.echarts-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-state, .empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text, .empty-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
207
pages/dashboard/PurchaseUserPie.uvue
Normal file
207
pages/dashboard/PurchaseUserPie.uvue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<view class="chart-container">
|
||||
<view class="chart-header">
|
||||
<view class="header-left">
|
||||
<view class="title-icon">
|
||||
<text class="iconfont">O</text>
|
||||
</view>
|
||||
<text class="chart-title">购买用户分析</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="chart-body">
|
||||
<view class="chart-view-wrapper">
|
||||
<EChartsView
|
||||
v-if="!loading && chartOption"
|
||||
:option="chartOption"
|
||||
class="echarts-view"
|
||||
/>
|
||||
|
||||
<!-- Loading 状态 -->
|
||||
<view v-if="loading" class="loading-state">
|
||||
<text class="loading-text">计算中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空数据状态 -->
|
||||
<view v-if="!loading && chartData.length === 0" class="empty-state">
|
||||
<text class="empty-text">无相关消费记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
import { PurchaseUserStat } from '@/types/charts.uts'
|
||||
|
||||
/**
|
||||
* PurchaseUserPie 购买用户统计饼图
|
||||
* 展示不同消费特征的用户分布情况
|
||||
*/
|
||||
|
||||
// --- 状态定义 ---
|
||||
const loading = ref(false)
|
||||
const chartData = ref<PurchaseUserStat[]>([])
|
||||
|
||||
// --- ECharts 配置计算属性 ---
|
||||
const chartOption = computed(() : any => {
|
||||
if (chartData.value.length === 0) return null
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
bottom: '5%',
|
||||
left: 'center',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666'
|
||||
}
|
||||
},
|
||||
// 视觉色彩方案
|
||||
color: ['#1890ff', '#36cfc9', '#ffc53d', '#ff4d4f'],
|
||||
series: [
|
||||
{
|
||||
name: '用户分布',
|
||||
type: 'pie',
|
||||
radius: ['45%', '70%'], // 采用环形图设计,更具现代感
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: {
|
||||
borderRadius: 6,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: '{b}\n{d}%',
|
||||
fontSize: 11,
|
||||
color: '#888'
|
||||
},
|
||||
emphasis: {
|
||||
scale: true,
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 12,
|
||||
length2: 10,
|
||||
smooth: true
|
||||
},
|
||||
data: chartData.value.map(item => {
|
||||
return {
|
||||
name: item.label,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// --- 数据获取逻辑 ---
|
||||
|
||||
/**
|
||||
* 模拟获取统计数据
|
||||
*/
|
||||
const fetchData = () => {
|
||||
loading.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
chartData.value = [
|
||||
{ label: '未消费用户', value: 342 } as PurchaseUserStat,
|
||||
{ label: '消费一次', value: 156 } as PurchaseUserStat,
|
||||
{ label: '留存客户', value: 218 } as PurchaseUserStat,
|
||||
{ label: '回流客户', value: 84 } as PurchaseUserStat
|
||||
]
|
||||
loading.value = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
refresh: fetchData
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f0f0f0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #fff7e6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-view-wrapper {
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.echarts-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-state, .empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text, .empty-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
370
pages/dashboard/UserTrendChart.uvue
Normal file
370
pages/dashboard/UserTrendChart.uvue
Normal file
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<view class="chart-container">
|
||||
<!-- 图表标题与切换 -->
|
||||
<view class="chart-header">
|
||||
<view class="header-left">
|
||||
<view class="title-icon">
|
||||
<text class="iconfont">≡</text>
|
||||
</view>
|
||||
<text class="chart-title">用户增长趋势</text>
|
||||
</view>
|
||||
|
||||
<view class="chart-controls">
|
||||
<view
|
||||
v-for="p in periods"
|
||||
:key="p.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: range === p.value }"
|
||||
@click="changeRange(p.value)"
|
||||
>
|
||||
<text class="seg-btn-text">{{ p.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图表内容 -->
|
||||
<view class="chart-body">
|
||||
<view class="axis-titles">
|
||||
<text class="axis-title">新增用户 (人)</text>
|
||||
</view>
|
||||
|
||||
<view class="chart-view-wrapper">
|
||||
<EChartsView
|
||||
v-if="!loading && chartOption"
|
||||
:option="chartOption"
|
||||
class="echarts-view"
|
||||
/>
|
||||
|
||||
<!-- Loading 状态 -->
|
||||
<view v-if="loading" class="loading-state">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空数据状态 -->
|
||||
<view v-if="!loading && trendData.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无趋势数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
import { UserTrend } from '@/types/charts.uts'
|
||||
|
||||
/**
|
||||
* UserTrendChart 用户趋势图
|
||||
* 展示日粒度用户增长,采用平滑曲线面积图
|
||||
*/
|
||||
|
||||
// --- 状态定义 ---
|
||||
const loading = ref(false)
|
||||
const range = ref('7d')
|
||||
const trendData = ref<UserTrend[]>([])
|
||||
|
||||
const periods = [
|
||||
{ label: '7天', value: '7d' },
|
||||
{ label: '30天', value: '30d' }
|
||||
]
|
||||
|
||||
// --- ECharts 配置计算属性 ---
|
||||
const chartOption = computed(() : any => {
|
||||
if (trendData.value.length === 0) return null
|
||||
|
||||
const xData = trendData.value.map(item => item.date)
|
||||
const yData = trendData.value.map(item => item.count)
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
lineStyle: { color: '#1890ff', type: 'dashed' }
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '5%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: xData,
|
||||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||||
axisLabel: {
|
||||
color: '#999',
|
||||
fontSize: 10,
|
||||
rotate: xData.length > 7 ? 45 : 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: { lineStyle: { type: 'dashed', color: '#f5f5f5' } },
|
||||
axisLabel: { color: '#999', fontSize: 10 }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'line',
|
||||
data: yData,
|
||||
smooth: true, // 平滑折线
|
||||
showSymbol: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
itemStyle: {
|
||||
color: '#1890ff',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#1890ff'
|
||||
},
|
||||
// 区域填充效果
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(24, 144, 255, 0.25)' },
|
||||
{ offset: 1, color: 'rgba(24, 144, 255, 0)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// --- 方法定义 ---
|
||||
|
||||
const changeRange = (v: string) => {
|
||||
range.value = v
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟接口请求
|
||||
* 使用 useRequest 模式的封装
|
||||
*/
|
||||
const fetchData = (r: string) => {
|
||||
loading.value = true
|
||||
|
||||
// 模拟业务逻辑
|
||||
setTimeout(() => {
|
||||
const mockData: UserTrend[] = []
|
||||
const count = r === '7d' ? 7 : 30
|
||||
const now = new Date()
|
||||
|
||||
for (let i = count - 1; i >= 0; i--) {
|
||||
const d = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
|
||||
mockData.push({
|
||||
date: `${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`,
|
||||
count: Math.floor(Math.random() * 80) + (i % 7 === 0 ? 50 : 20)
|
||||
} as UserTrend)
|
||||
}
|
||||
|
||||
trendData.value = mockData
|
||||
loading.value = false
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// 暴露方法给外部调用
|
||||
defineExpose({
|
||||
refresh: () => fetchData(range.value)
|
||||
})
|
||||
|
||||
// --- 生命周期与监听 ---
|
||||
onMounted(() => {
|
||||
fetchData(range.value)
|
||||
})
|
||||
|
||||
watch(range, (newVal) => {
|
||||
fetchData(newVal)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f0f0f0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #e6f7ff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.seg-btn {
|
||||
height: 26px;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.seg-btn:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.seg-btn.active {
|
||||
background: #1890ff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
.seg-btn-text {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.seg-btn.active .seg-btn-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.axis-titles {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.axis-title {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chart-view-wrapper {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.echarts-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-state, .empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text, .empty-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
// --- Data Fetching ---
|
||||
const fetchTrendData = () => {
|
||||
loading.value = true
|
||||
// 模拟 API 请求: GET /api/user/trend
|
||||
setTimeout(() => {
|
||||
const mock: UserTrend[] = []
|
||||
const now = new Date()
|
||||
for (let i = 14; i >= 0; i--) {
|
||||
const d = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
|
||||
mock.push({
|
||||
date: `${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`,
|
||||
count: Math.floor(Math.random() * 50) + 20 + (i === 5 ? 100 : 0)
|
||||
})
|
||||
}
|
||||
trendData.value = mock
|
||||
loading.value = false
|
||||
}, 600)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTrendData()
|
||||
})
|
||||
|
||||
// 暴露刷新接口
|
||||
defineExpose({
|
||||
refresh: fetchTrendData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.echarts-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-overlay, .empty-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255,255,255,0.6);
|
||||
}
|
||||
|
||||
.loading-text, .empty-text {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
185
pages/mall/admin/article/article.uts
Normal file
185
pages/mall/admin/article/article.uts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 文章管理服务层
|
||||
* 提供文章列表、详情、保存、删除等接口
|
||||
*/
|
||||
|
||||
// 文章列表项数据结构
|
||||
export interface ArticleItem {
|
||||
id: number
|
||||
title: string
|
||||
category_id: number
|
||||
category_name: string
|
||||
image: string
|
||||
description: string
|
||||
status: number // 0: 未发布, 1: 已发布
|
||||
views: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 文章详情数据结构
|
||||
export interface ArticleDetail {
|
||||
id: number
|
||||
title: string
|
||||
category_id: number
|
||||
image: string
|
||||
description: string
|
||||
content: string
|
||||
status: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 文章创建/编辑参数
|
||||
export interface ArticlePayload {
|
||||
title: string
|
||||
category_id: number
|
||||
image: string
|
||||
description: string
|
||||
content: string
|
||||
status: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
* @param params 查询参数 { page, limit, keyword, status, category_id }
|
||||
* @returns Promise<{ items: ArticleItem[], total: number }>
|
||||
*/
|
||||
export function getArticleList(params: any = {}): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.get('/article/list', { params })
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
title: '如何选择合适的商品分类',
|
||||
category_id: 1,
|
||||
category_name: '运营指南',
|
||||
image: '/static/article-1.png',
|
||||
description: '商品分类是电商平台的重要组成部分...',
|
||||
status: 1,
|
||||
views: 128,
|
||||
created_at: '2026-01-28 10:30:00',
|
||||
updated_at: '2026-01-28 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '商城营销活动最佳实践',
|
||||
category_id: 2,
|
||||
category_name: '营销技巧',
|
||||
image: '/static/article-2.png',
|
||||
description: '分享最新的营销活动策略和案例...',
|
||||
status: 1,
|
||||
views: 256,
|
||||
created_at: '2026-01-27 15:20:00',
|
||||
updated_at: '2026-01-27 15:20:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '用户评价管理指南',
|
||||
category_id: 1,
|
||||
category_name: '运营指南',
|
||||
image: '/static/article-3.png',
|
||||
description: '如何有效管理用户的评价和反馈...',
|
||||
status: 0,
|
||||
views: 64,
|
||||
created_at: '2026-01-26 09:15:00',
|
||||
updated_at: '2026-01-26 09:15:00'
|
||||
}
|
||||
],
|
||||
total: 3
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章详情
|
||||
* @param id 文章ID
|
||||
* @returns Promise<ArticleDetail>
|
||||
*/
|
||||
export function getArticleDetail(id: number): Promise<ArticleDetail> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.get(`/article/${id}`)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
id,
|
||||
title: '如何选择合适的商品分类',
|
||||
category_id: 1,
|
||||
image: '/static/article-1.png',
|
||||
description: '商品分类是电商平台的重要组成部分...',
|
||||
content: '<h2>标题</h2><p>详细内容...</p>',
|
||||
status: 1,
|
||||
created_at: '2026-01-28 10:30:00',
|
||||
updated_at: '2026-01-28 10:30:00'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文章(新建或编辑)
|
||||
* @param data 文章数据
|
||||
* @param id 文章ID(编辑时传入)
|
||||
* @returns Promise<{ success: boolean, message: string, id?: number }>
|
||||
*/
|
||||
export function saveArticle(data: ArticlePayload, id?: number): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// const method = id ? 'PUT' : 'POST'
|
||||
// const url = id ? `/article/${id}` : '/article'
|
||||
// return uni.$http[method === 'PUT' ? 'put' : 'post'](url, data)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: id ? '编辑成功' : '新建成功',
|
||||
id: id || Math.floor(Math.random() * 10000)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章
|
||||
* @param id 文章ID
|
||||
* @returns Promise<{ success: boolean, message: string }>
|
||||
*/
|
||||
export function deleteArticle(id: number): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.delete(`/article/${id}`)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: '删除成功'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布/取消发布文章
|
||||
* @param id 文章ID
|
||||
* @param status 状态 (0: 取消发布, 1: 发布)
|
||||
* @returns Promise<{ success: boolean, message: string }>
|
||||
*/
|
||||
export function publishArticle(id: number, status: number): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.put(`/article/${id}/publish`, { status })
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: status === 1 ? '发布成功' : '取消发布成功'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
178
pages/mall/admin/article/articleCategory.uts
Normal file
178
pages/mall/admin/article/articleCategory.uts
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 文章分类管理服务层
|
||||
* 提供分类列表、保存、删除等接口
|
||||
*/
|
||||
|
||||
// 分类数据结构
|
||||
export interface CategoryItem {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
image: string
|
||||
article_count: number
|
||||
sort: number
|
||||
status: number // 0: 禁用, 1: 启用
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 分类创建/编辑参数
|
||||
export interface CategoryPayload {
|
||||
name: string
|
||||
description: string
|
||||
image: string
|
||||
sort: number
|
||||
status: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
* @param params 查询参数 { page, limit, keyword, status }
|
||||
* @returns Promise<{ items: CategoryItem[], total: number }>
|
||||
*/
|
||||
export function getCategoryList(params: any = {}): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.get('/article/category/list', { params })
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
name: '运营指南',
|
||||
description: '关于商城运营的各类指南和教程',
|
||||
image: '/static/category-1.png',
|
||||
article_count: 12,
|
||||
sort: 1,
|
||||
status: 1,
|
||||
created_at: '2026-01-15 10:00:00',
|
||||
updated_at: '2026-01-20 15:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '营销技巧',
|
||||
description: '营销活动策略和最佳实践',
|
||||
image: '/static/category-2.png',
|
||||
article_count: 8,
|
||||
sort: 2,
|
||||
status: 1,
|
||||
created_at: '2026-01-15 11:00:00',
|
||||
updated_at: '2026-01-19 14:20:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '常见问题',
|
||||
description: '常见问题解答和故障排除',
|
||||
image: '/static/category-3.png',
|
||||
article_count: 5,
|
||||
sort: 3,
|
||||
status: 1,
|
||||
created_at: '2026-01-15 12:00:00',
|
||||
updated_at: '2026-01-18 09:45:00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '产品更新',
|
||||
description: '产品更新日志和新功能介绍',
|
||||
image: '/static/category-4.png',
|
||||
article_count: 3,
|
||||
sort: 4,
|
||||
status: 0,
|
||||
created_at: '2026-01-16 10:00:00',
|
||||
updated_at: '2026-01-17 16:00:00'
|
||||
}
|
||||
],
|
||||
total: 4
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类详情
|
||||
* @param id 分类ID
|
||||
* @returns Promise<CategoryItem>
|
||||
*/
|
||||
export function getCategoryDetail(id: number): Promise<CategoryItem> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.get(`/article/category/${id}`)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
id,
|
||||
name: '运营指南',
|
||||
description: '关于商城运营的各类指南和教程',
|
||||
image: '/static/category-1.png',
|
||||
article_count: 12,
|
||||
sort: 1,
|
||||
status: 1,
|
||||
created_at: '2026-01-15 10:00:00',
|
||||
updated_at: '2026-01-20 15:30:00'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存分类(新建或编辑)
|
||||
* @param data 分类数据
|
||||
* @param id 分类ID(编辑时传入)
|
||||
* @returns Promise<{ success: boolean, message: string, id?: number }>
|
||||
*/
|
||||
export function saveCategory(data: CategoryPayload, id?: number): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// const method = id ? 'PUT' : 'POST'
|
||||
// const url = id ? `/article/category/${id}` : '/article/category'
|
||||
// return uni.$http[method === 'PUT' ? 'put' : 'post'](url, data)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: id ? '编辑成功' : '新建成功',
|
||||
id: id || Math.floor(Math.random() * 10000)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
* @param id 分类ID
|
||||
* @returns Promise<{ success: boolean, message: string }>
|
||||
*/
|
||||
export function deleteCategory(id: number): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.delete(`/article/category/${id}`)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: '删除成功'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用分类
|
||||
* @param id 分类ID
|
||||
* @param status 状态 (0: 禁用, 1: 启用)
|
||||
* @returns Promise<{ success: boolean, message: string }>
|
||||
*/
|
||||
export function toggleCategoryStatus(id: number, status: number): Promise<any> {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// return uni.$http.put(`/article/category/${id}/status`, { status })
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: status === 1 ? '启用成功' : '禁用成功'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
25
pages/mall/admin/article/category.uvue
Normal file
25
pages/mall/admin/article/category.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('category')
|
||||
const title = ref<string>('category')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/article/create.uvue
Normal file
25
pages/mall/admin/article/create.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('create')
|
||||
const title = ref<string>('create')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/article/edit.uvue
Normal file
25
pages/mall/admin/article/edit.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('edit')
|
||||
const title = ref<string>('edit')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/article/index.uvue
Normal file
25
pages/mall/admin/article/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('article-index')
|
||||
const title = ref<string>('文章管理')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
81
pages/mall/admin/cms/article/list.uvue
Normal file
81
pages/mall/admin/cms/article/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">文章管理</text>
|
||||
<text class="page-subtitle">Component: CmsArticle</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 文章管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/cms/category/list.uvue
Normal file
81
pages/mall/admin/cms/category/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">文章分类</text>
|
||||
<text class="page-subtitle">Component: CmsCategory</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 文章分类 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
179
pages/mall/admin/design/README.md
Normal file
179
pages/mall/admin/design/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# 页面装修管理模块 - README
|
||||
|
||||
> 基于CRMEB项目标准,实现完整的页面装修和DIY功能
|
||||
|
||||
## 📋 文件结构
|
||||
|
||||
```
|
||||
pages/mall/admin/design/
|
||||
├── index.uvue # 装修管理主界面(898行)
|
||||
├── design.uts # 业务逻辑和数据管理(350+行)
|
||||
├── editor.uvue # 装修编辑器(待实现)
|
||||
├── preview.uvue # 装修预览页面(待实现)
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
### 1. 首页装修 (Homepage)
|
||||
|
||||
- 自定义首页布局和显示内容
|
||||
- 支持轮播图、商品展示、文本等组件
|
||||
- 实时预览效果
|
||||
- 版本管理和发布
|
||||
|
||||
### 2. 分类页装修 (Category)
|
||||
|
||||
- 为不同商品分类创建装修页面
|
||||
- 支持多套分类装修方案
|
||||
- 按分类自动应用装修效果
|
||||
- 快速切换和对比
|
||||
|
||||
### 3. 商品页装修 (Product)
|
||||
|
||||
- 自定义商品详情页布局
|
||||
- 支持商品图、信息、评价等模块
|
||||
- 提升商品转化率
|
||||
- A/B测试支持
|
||||
|
||||
### 4. 自定义页面 (Custom)
|
||||
|
||||
- 创建和管理自定义营销页面
|
||||
- 灵活的页面路径设置
|
||||
- 独立的装修配置
|
||||
- 活动和推广专用
|
||||
|
||||
### 5. 页面模板库 (Templates)
|
||||
|
||||
- 预设4套电商风格模板
|
||||
- 一键应用模板快速建站
|
||||
- 模板库不断扩充
|
||||
- 自定义模板保存
|
||||
|
||||
### 6. 组件库 (Components)
|
||||
|
||||
- 8种预设装修组件:
|
||||
- 图片组件 (Image)
|
||||
- 文本组件 (Text)
|
||||
- 商品组件 (Product)
|
||||
- 轮播组件 (Carousel)
|
||||
- 分割线 (Divider)
|
||||
- 间距组件 (Spacer)
|
||||
- 按钮组件 (Button)
|
||||
- 表单组件 (Form)
|
||||
|
||||
## 🔧 API 函数列表
|
||||
|
||||
| 函数 | 参数 | 返回值 | 说明 |
|
||||
| -------------------------- | ---------- | -------------------------- | ---------------- |
|
||||
| `getDesignList(params?)` | 查询参数 | Promise<DesignItem[]> | 获取装修列表 |
|
||||
| `getHomePageDesign()` | 无 | Promise<DesignItem> | 获取首页装修 |
|
||||
| `getProductPageDesign()` | 无 | Promise<DesignItem> | 获取商品页装修 |
|
||||
| `getCategoryDesigns()` | 无 | Promise<DesignItem[]> | 获取分类装修列表 |
|
||||
| `getCustomPages()` | 无 | Promise<DesignItem[]> | 获取自定义页面 |
|
||||
| `getTemplateLibrary()` | 无 | Promise<DesignTemplate[]> | 获取模板库 |
|
||||
| `getAvailableComponents()` | 无 | Promise<DesignComponent[]> | 获取可用组件 |
|
||||
| `saveDesign(design)` | DesignItem | Promise<{id, message}> | 保存装修 |
|
||||
| `publishDesign(id)` | 装修ID | Promise<{message}> | 发布装修 |
|
||||
| `deleteDesign(id)` | 装修ID | Promise<{message}> | 删除装修 |
|
||||
|
||||
## 📊 数据结构
|
||||
|
||||
### DesignItem 装修页面
|
||||
|
||||
```typescript
|
||||
interface DesignItem {
|
||||
id: string | number; // 装修ID
|
||||
name: string; // 装修名称
|
||||
type: "homepage" | "category" | "product" | "custom";
|
||||
status: 0 | 1; // 0=草稿, 1=已发布
|
||||
content: DesignComponent[]; // 组件内容
|
||||
categoryId?: string | number; // 分类ID
|
||||
categoryName?: string; // 分类名称
|
||||
path?: string; // 页面路径
|
||||
version?: string; // 版本号
|
||||
created_at?: string; // 创建时间
|
||||
updated_at?: string; // 更新时间
|
||||
}
|
||||
```
|
||||
|
||||
### DesignComponent 组件配置
|
||||
|
||||
```typescript
|
||||
interface DesignComponent {
|
||||
id: string; // 组件ID
|
||||
type:
|
||||
| "image"
|
||||
| "text"
|
||||
| "product"
|
||||
| "carousel"
|
||||
| "divider"
|
||||
| "spacer"
|
||||
| "button"
|
||||
| "form";
|
||||
name: string; // 组件名称
|
||||
icon: string; // 组件图标
|
||||
description?: string; // 组件描述
|
||||
config?: Record<string, any>; // 配置参数
|
||||
children?: DesignComponent[]; // 子组件
|
||||
}
|
||||
```
|
||||
|
||||
## 💻 使用示例
|
||||
|
||||
```typescript
|
||||
// 导入服务
|
||||
import {
|
||||
getDesignList,
|
||||
saveDesign,
|
||||
publishDesign,
|
||||
getAvailableComponents,
|
||||
} from "./design.uts";
|
||||
|
||||
// 获取列表
|
||||
const designs = await getDesignList();
|
||||
|
||||
// 保存装修
|
||||
await saveDesign({
|
||||
id: 1,
|
||||
name: "首页",
|
||||
type: "homepage",
|
||||
status: 0,
|
||||
content: [],
|
||||
});
|
||||
|
||||
// 发布装修
|
||||
await publishDesign(1);
|
||||
|
||||
// 获取组件库
|
||||
const components = await getAvailableComponents();
|
||||
```
|
||||
|
||||
## 📱 菜单配置
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "design",
|
||||
"title": "设计",
|
||||
"children": [
|
||||
{
|
||||
"id": "design-home",
|
||||
"title": "页面装修",
|
||||
"path": "/pages/mall/admin/design/index"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 后续开发
|
||||
|
||||
- [ ] editor.uvue - 装修编辑器
|
||||
- [ ] preview.uvue - 装修预览
|
||||
- [ ] 拖拽排序功能
|
||||
- [ ] 版本管理
|
||||
- [ ] 模板库管理
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-01-31
|
||||
**版本**: 1.0.0
|
||||
25
pages/mall/admin/design/category.uvue
Normal file
25
pages/mall/admin/design/category.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('design-category')
|
||||
const title = ref<string>('分类页装修')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/design/components.uvue
Normal file
25
pages/mall/admin/design/components.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('category')
|
||||
const title = ref<string>('category')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
439
pages/mall/admin/design/config.uts
Normal file
439
pages/mall/admin/design/config.uts
Normal file
@@ -0,0 +1,439 @@
|
||||
/**
|
||||
* 设计模块页面路由配置与数据
|
||||
* 将 design.uts 的函数输出转换为页面路由/配置格式
|
||||
*/
|
||||
|
||||
import type { DesignItem, DesignComponent, DesignTemplate } from './design.uts'
|
||||
|
||||
/**
|
||||
* 装修页面列表路由配置
|
||||
*/
|
||||
export const designListPageConfig = {
|
||||
id: 'design-list',
|
||||
path: '/pages/mall/admin/design/list',
|
||||
name: '装修列表',
|
||||
title: '装修管理',
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '首页装修',
|
||||
type: 'homepage' as const,
|
||||
status: 1,
|
||||
path: '/pages/mall/admin/design/index?tab=homepage',
|
||||
content: [],
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '年货节活动页',
|
||||
type: 'custom' as const,
|
||||
status: 1,
|
||||
path: '/pages/mall/admin/design/index?tab=custom',
|
||||
content: [],
|
||||
updated_at: '2026-01-28 10:15:00'
|
||||
}
|
||||
] as DesignItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页装修页面路由配置
|
||||
*/
|
||||
export const designHomepagePageConfig = {
|
||||
id: 'design-homepage',
|
||||
path: '/pages/mall/admin/design/index?tab=homepage',
|
||||
name: '首页装修',
|
||||
title: '首页装修 - 打造吸引人的商城首页',
|
||||
data: {
|
||||
id: 'homepage',
|
||||
name: '首页装修',
|
||||
type: 'homepage' as const,
|
||||
status: 1,
|
||||
content: [
|
||||
{
|
||||
id: 'carousel-1',
|
||||
type: 'carousel' as const,
|
||||
name: '轮播图',
|
||||
icon: 'C',
|
||||
description: '首页顶部轮播图展示',
|
||||
config: {
|
||||
autoplay: true,
|
||||
duration: 5000,
|
||||
height: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'product-1',
|
||||
type: 'product' as const,
|
||||
name: '商品展示',
|
||||
icon: 'P',
|
||||
description: '热销商品列表',
|
||||
config: {
|
||||
count: 8,
|
||||
columns: 2,
|
||||
layout: 'grid'
|
||||
}
|
||||
}
|
||||
],
|
||||
version: '1.0.0',
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
} as DesignItem
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类页装修页面路由配置
|
||||
*/
|
||||
export const designCategoryPageConfig = {
|
||||
id: 'design-category',
|
||||
path: '/pages/mall/admin/design/index?tab=category',
|
||||
name: '分类页装修',
|
||||
title: '分类页装修 - 为不同分类创建独特展示',
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '默认分类装修',
|
||||
type: 'category' as const,
|
||||
status: 1,
|
||||
categoryId: 0,
|
||||
categoryName: '全部分类',
|
||||
path: '/pages/mall/admin/design/index?tab=category&id=1',
|
||||
content: [],
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '热销商品分类',
|
||||
type: 'category' as const,
|
||||
status: 0,
|
||||
categoryId: 1,
|
||||
categoryName: '推荐分类',
|
||||
path: '/pages/mall/admin/design/index?tab=category&id=2',
|
||||
content: [],
|
||||
updated_at: '2026-01-29 10:15:00'
|
||||
}
|
||||
] as DesignItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品页装修页面路由配置
|
||||
*/
|
||||
export const designProductPageConfig = {
|
||||
id: 'design-product',
|
||||
path: '/pages/mall/admin/design/index?tab=product',
|
||||
name: '商品页装修',
|
||||
title: '商品页装修 - 自定义商品详情页展示',
|
||||
data: {
|
||||
id: 'product',
|
||||
name: '商品页装修',
|
||||
type: 'product' as const,
|
||||
status: 1,
|
||||
content: [
|
||||
{
|
||||
id: 'image-1',
|
||||
type: 'image' as const,
|
||||
name: '商品图',
|
||||
icon: 'I',
|
||||
description: '商品主图展示'
|
||||
},
|
||||
{
|
||||
id: 'product-info',
|
||||
type: 'text' as const,
|
||||
name: '商品信息',
|
||||
icon: 'T',
|
||||
description: '商品名称和价格'
|
||||
}
|
||||
],
|
||||
version: '1.0.0',
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
} as DesignItem
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义页面路由配置
|
||||
*/
|
||||
export const designCustomPageConfig = {
|
||||
id: 'design-custom',
|
||||
path: '/pages/mall/admin/design/index?tab=custom',
|
||||
name: '自定义页面',
|
||||
title: '自定义页面 - 创建特殊内容页面',
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '新年促销页',
|
||||
type: 'custom' as const,
|
||||
status: 1,
|
||||
path: '/pages/mall/admin/design/index?tab=custom&id=1',
|
||||
content: [],
|
||||
updated_at: '2026-01-28 09:00:00'
|
||||
}
|
||||
] as DesignItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板库页面路由配置
|
||||
*/
|
||||
export const designTemplatePageConfig = {
|
||||
id: 'design-templates',
|
||||
path: '/pages/mall/admin/design/index?tab=templates',
|
||||
name: '模板库',
|
||||
title: '模板库 - 选择预设装修模板',
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '电商风格A',
|
||||
description: '简洁现代的电商布局',
|
||||
type: 'homepage',
|
||||
preview: '/static/images/template-a.png',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '电商风格B',
|
||||
description: '豪华展示的电商布局',
|
||||
type: 'homepage',
|
||||
preview: '/static/images/template-b.png',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '精品风格',
|
||||
description: '精品商品展示布局',
|
||||
type: 'homepage',
|
||||
preview: '/static/images/template-c.png',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '商城风格',
|
||||
description: '完整商城功能布局',
|
||||
type: 'homepage',
|
||||
preview: '/static/images/template-d.png',
|
||||
content: []
|
||||
}
|
||||
] as DesignTemplate[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件库页面路由配置
|
||||
*/
|
||||
export const designComponentPageConfig = {
|
||||
id: 'design-components',
|
||||
path: '/pages/mall/admin/design/index?tab=components',
|
||||
name: '组件库',
|
||||
title: '组件库 - 丰富的页面组件',
|
||||
data: [
|
||||
{
|
||||
id: 'image',
|
||||
type: 'image' as const,
|
||||
name: '图片组件',
|
||||
icon: 'I',
|
||||
description: '展示图片和图片轮播',
|
||||
componentName: 'ImageComponent',
|
||||
config: {
|
||||
defaultWidth: '100%',
|
||||
defaultHeight: 'auto'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
type: 'text' as const,
|
||||
name: '文本组件',
|
||||
icon: 'T',
|
||||
description: '展示文本内容和段落',
|
||||
componentName: 'TextComponent'
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
type: 'product' as const,
|
||||
name: '商品组件',
|
||||
icon: 'P',
|
||||
description: '展示商品列表和推荐',
|
||||
componentName: 'ProductComponent',
|
||||
config: {
|
||||
defaultCount: 6,
|
||||
defaultColumns: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'carousel',
|
||||
type: 'carousel' as const,
|
||||
name: '轮播组件',
|
||||
icon: 'C',
|
||||
description: '图片和内容轮播',
|
||||
componentName: 'CarouselComponent',
|
||||
config: {
|
||||
autoplay: true,
|
||||
duration: 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'divider',
|
||||
type: 'divider' as const,
|
||||
name: '分割线',
|
||||
icon: 'D',
|
||||
description: '分割不同内容区域',
|
||||
componentName: 'DividerComponent'
|
||||
},
|
||||
{
|
||||
id: 'spacer',
|
||||
type: 'spacer' as const,
|
||||
name: '间距组件',
|
||||
icon: 'S',
|
||||
description: '调整元素间距',
|
||||
componentName: 'SpacerComponent',
|
||||
config: {
|
||||
defaultHeight: 16
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'button',
|
||||
type: 'button' as const,
|
||||
name: '按钮组件',
|
||||
icon: 'B',
|
||||
description: '创建点击按钮',
|
||||
componentName: 'ButtonComponent'
|
||||
},
|
||||
{
|
||||
id: 'form',
|
||||
type: 'form' as const,
|
||||
name: '表单组件',
|
||||
icon: 'F',
|
||||
description: '收集用户输入数据',
|
||||
componentName: 'FormComponent'
|
||||
}
|
||||
] as DesignComponent[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑页面路由配置
|
||||
*/
|
||||
export const designEditorPageConfig = {
|
||||
id: 'design-editor',
|
||||
path: '/pages/mall/admin/design/editor',
|
||||
name: '装修编辑器',
|
||||
title: '装修编辑器 - 可视化编辑装修页面',
|
||||
components: [
|
||||
{
|
||||
id: 'canvas',
|
||||
name: '编辑画布',
|
||||
description: '拖拽编辑区域'
|
||||
},
|
||||
{
|
||||
id: 'sidebar',
|
||||
name: '组件侧栏',
|
||||
description: '可用组件列表'
|
||||
},
|
||||
{
|
||||
id: 'properties',
|
||||
name: '属性面板',
|
||||
description: '组件属性编辑'
|
||||
},
|
||||
{
|
||||
id: 'preview',
|
||||
name: '预览窗口',
|
||||
description: '实时效果预览'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览页面路由配置
|
||||
*/
|
||||
export const designPreviewPageConfig = {
|
||||
id: 'design-preview',
|
||||
path: '/pages/mall/design/preview/:id',
|
||||
name: '装修预览',
|
||||
title: '装修效果预览',
|
||||
features: [
|
||||
'全屏预览',
|
||||
'响应式展示',
|
||||
'交互测试',
|
||||
'性能分析'
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有设计页面路由配置
|
||||
*/
|
||||
export const allDesignPageConfigs = [
|
||||
designListPageConfig,
|
||||
designHomepagePageConfig,
|
||||
designCategoryPageConfig,
|
||||
designProductPageConfig,
|
||||
designCustomPageConfig,
|
||||
designTemplatePageConfig,
|
||||
designComponentPageConfig,
|
||||
designEditorPageConfig,
|
||||
designPreviewPageConfig
|
||||
]
|
||||
|
||||
/**
|
||||
* 根据 tab 获取对应的页面配置
|
||||
*/
|
||||
export function getDesignPageConfig(tab: string) {
|
||||
const configMap: Record<string, any> = {
|
||||
'homepage': designHomepagePageConfig,
|
||||
'category': designCategoryPageConfig,
|
||||
'product': designProductPageConfig,
|
||||
'custom': designCustomPageConfig,
|
||||
'templates': designTemplatePageConfig,
|
||||
'components': designComponentPageConfig,
|
||||
'editor': designEditorPageConfig,
|
||||
'preview': designPreviewPageConfig,
|
||||
'list': designListPageConfig
|
||||
}
|
||||
return configMap[tab] || designListPageConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* 装修页面导航菜单结构
|
||||
*/
|
||||
export const designMenuStructure = {
|
||||
id: 'design',
|
||||
title: '设计',
|
||||
icon: '/static/design.svg',
|
||||
path: '/pages/mall/admin/design/index',
|
||||
children: [
|
||||
{
|
||||
id: 'page-decoration',
|
||||
title: '页面装修',
|
||||
children: [
|
||||
{
|
||||
id: 'design-homepage',
|
||||
title: '首页装修',
|
||||
path: '/pages/mall/admin/design/index?tab=homepage'
|
||||
},
|
||||
{
|
||||
id: 'design-category',
|
||||
title: '分类页装修',
|
||||
path: '/pages/mall/admin/design/index?tab=category'
|
||||
},
|
||||
{
|
||||
id: 'design-product',
|
||||
title: '商品页装修',
|
||||
path: '/pages/mall/admin/design/index?tab=product'
|
||||
},
|
||||
{
|
||||
id: 'design-custom',
|
||||
title: '自定义页面',
|
||||
path: '/pages/mall/admin/design/index?tab=custom'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'design-library',
|
||||
title: '设计库',
|
||||
children: [
|
||||
{
|
||||
id: 'design-templates',
|
||||
title: '模板库',
|
||||
path: '/pages/mall/admin/design/index?tab=templates'
|
||||
},
|
||||
{
|
||||
id: 'design-components',
|
||||
title: '组件库',
|
||||
path: '/pages/mall/admin/design/index?tab=components'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
25
pages/mall/admin/design/custom.uvue
Normal file
25
pages/mall/admin/design/custom.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('custom')
|
||||
const title = ref<string>('custom')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
38
pages/mall/admin/design/data-config.uvue
Normal file
38
pages/mall/admin/design/data-config.uvue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<AdminLayout current-page="design-data">
|
||||
<view class="admin-main">
|
||||
<view class="header">
|
||||
<text class="title">数据配置</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text>商城数据配置(建设中)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-main {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
549
pages/mall/admin/design/design.uts
Normal file
549
pages/mall/admin/design/design.uts
Normal file
@@ -0,0 +1,549 @@
|
||||
/**
|
||||
* 页面装修业务逻辑模块
|
||||
* 参考CRMEB项目,提供完整的装修管理功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* 装修页面数据接口
|
||||
*/
|
||||
export interface DesignItem {
|
||||
id: string | number
|
||||
name: string
|
||||
type: 'homepage' | 'category' | 'product' | 'custom'
|
||||
status: 0 | 1 // 0: 草稿, 1: 已发布
|
||||
categoryId?: string | number
|
||||
categoryName?: string
|
||||
path?: string
|
||||
preview_url?: string
|
||||
content: DesignComponent[]
|
||||
version?: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 装修组件接口
|
||||
*/
|
||||
export interface DesignComponent {
|
||||
id: string
|
||||
type: 'image' | 'text' | 'product' | 'carousel' | 'divider' | 'spacer' | 'button' | 'form'
|
||||
name: string
|
||||
icon: string
|
||||
description?: string
|
||||
componentName?: string
|
||||
config?: Record<string, any>
|
||||
children?: DesignComponent[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 装修模板接口
|
||||
*/
|
||||
export interface DesignTemplate {
|
||||
id: string | number
|
||||
name: string
|
||||
description: string
|
||||
type: string
|
||||
preview: string
|
||||
content: DesignComponent[]
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取装修页面列表
|
||||
* @param params 查询参数
|
||||
* @returns 装修页面列表
|
||||
*/
|
||||
export function getDesignList(params?: Record<string, any>): Promise<DesignItem[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: 实际应调用后端API
|
||||
const designList: DesignItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '首页装修',
|
||||
type: 'homepage',
|
||||
status: 1,
|
||||
content: [],
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '年货节活动页',
|
||||
type: 'custom',
|
||||
status: 1,
|
||||
content: [],
|
||||
updated_at: '2026-01-28 10:15:00'
|
||||
}
|
||||
]
|
||||
setTimeout(() => resolve(designList), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页装修详情
|
||||
* @returns 首页装修数据
|
||||
*/
|
||||
export function getHomePageDesign(): Promise<DesignItem> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const homepage: DesignItem = {
|
||||
id: 'homepage',
|
||||
name: '首页装修',
|
||||
type: 'homepage',
|
||||
status: 1,
|
||||
content: [
|
||||
{
|
||||
id: 'carousel-1',
|
||||
type: 'carousel',
|
||||
name: '轮播图',
|
||||
icon: 'C',
|
||||
description: '首页顶部轮播图展示',
|
||||
config: {
|
||||
autoplay: true,
|
||||
duration: 5000,
|
||||
height: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'product-1',
|
||||
type: 'product',
|
||||
name: '商品展示',
|
||||
icon: 'P',
|
||||
description: '热销商品列表',
|
||||
config: {
|
||||
count: 8,
|
||||
columns: 2,
|
||||
layout: 'grid'
|
||||
}
|
||||
}
|
||||
],
|
||||
version: '1.0.0',
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
}
|
||||
setTimeout(() => resolve(homepage), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品页装修详情
|
||||
* @returns 商品页装修数据
|
||||
*/
|
||||
export function getProductPageDesign(): Promise<DesignItem> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const productPage: DesignItem = {
|
||||
id: 'product',
|
||||
name: '商品页装修',
|
||||
type: 'product',
|
||||
status: 1,
|
||||
content: [
|
||||
{
|
||||
id: 'image-1',
|
||||
type: 'image',
|
||||
name: '商品图',
|
||||
icon: 'I',
|
||||
description: '商品主图展示'
|
||||
},
|
||||
{
|
||||
id: 'product-info',
|
||||
type: 'text',
|
||||
name: '商品信息',
|
||||
icon: 'T',
|
||||
description: '商品名称和价格'
|
||||
}
|
||||
],
|
||||
version: '1.0.0',
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
}
|
||||
setTimeout(() => resolve(productPage), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类装修列表
|
||||
* @returns 分类装修列表
|
||||
*/
|
||||
export function getCategoryDesigns(): Promise<DesignItem[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const categories: DesignItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '默认分类装修',
|
||||
type: 'category',
|
||||
status: 1,
|
||||
categoryId: 0,
|
||||
categoryName: '全部分类',
|
||||
content: [],
|
||||
updated_at: '2026-01-30 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '热销商品分类',
|
||||
type: 'category',
|
||||
status: 0,
|
||||
categoryId: 1,
|
||||
categoryName: '推荐分类',
|
||||
content: [],
|
||||
updated_at: '2026-01-29 10:15:00'
|
||||
}
|
||||
]
|
||||
setTimeout(() => resolve(categories), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义页面列表
|
||||
* @returns 自定义页面列表
|
||||
*/
|
||||
export function getCustomPages(): Promise<DesignItem[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const customPages: DesignItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '新年促销页',
|
||||
type: 'custom',
|
||||
status: 1,
|
||||
path: '/pages/promotion/newyear',
|
||||
content: [],
|
||||
updated_at: '2026-01-28 09:00:00'
|
||||
}
|
||||
]
|
||||
setTimeout(() => resolve(customPages), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面模板库
|
||||
* @returns 模板列表
|
||||
*/
|
||||
export function getTemplateLibrary(): Promise<DesignTemplate[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const templates: DesignTemplate[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '电商风格A',
|
||||
description: '简洁现代的电商布局',
|
||||
type: 'homepage',
|
||||
preview: '@/static/images/template-a.png',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '电商风格B',
|
||||
description: '豪华展示的电商布局',
|
||||
type: 'homepage',
|
||||
preview: '@/static/images/template-b.png',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '精品风格',
|
||||
description: '精品商品展示布局',
|
||||
type: 'homepage',
|
||||
preview: '@/static/images/template-c.png',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '商城风格',
|
||||
description: '完整商城功能布局',
|
||||
type: 'homepage',
|
||||
preview: '@/static/images/template-d.png',
|
||||
content: []
|
||||
}
|
||||
]
|
||||
setTimeout(() => resolve(templates), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用组件库
|
||||
* @returns 组件列表
|
||||
*/
|
||||
export function getAvailableComponents(): Promise<DesignComponent[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const components: DesignComponent[] = [
|
||||
{
|
||||
id: 'image',
|
||||
type: 'image',
|
||||
name: '图片组件',
|
||||
icon: 'I',
|
||||
description: '展示图片和图片轮播',
|
||||
componentName: 'ImageComponent',
|
||||
config: {
|
||||
defaultWidth: '100%',
|
||||
defaultHeight: 'auto'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
type: 'text',
|
||||
name: '文本组件',
|
||||
icon: 'T',
|
||||
description: '展示文本内容和段落',
|
||||
componentName: 'TextComponent'
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
type: 'product',
|
||||
name: '商品组件',
|
||||
icon: 'P',
|
||||
description: '展示商品列表和推荐',
|
||||
componentName: 'ProductComponent',
|
||||
config: {
|
||||
defaultCount: 6,
|
||||
defaultColumns: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'carousel',
|
||||
type: 'carousel',
|
||||
name: '轮播组件',
|
||||
icon: 'C',
|
||||
description: '图片和内容轮播',
|
||||
componentName: 'CarouselComponent',
|
||||
config: {
|
||||
autoplay: true,
|
||||
duration: 5000
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'divider',
|
||||
type: 'divider',
|
||||
name: '分割线',
|
||||
icon: 'D',
|
||||
description: '分割不同内容区域',
|
||||
componentName: 'DividerComponent'
|
||||
},
|
||||
{
|
||||
id: 'spacer',
|
||||
type: 'spacer',
|
||||
name: '间距组件',
|
||||
icon: 'S',
|
||||
description: '调整元素间距',
|
||||
componentName: 'SpacerComponent',
|
||||
config: {
|
||||
defaultHeight: 16
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'button',
|
||||
type: 'button',
|
||||
name: '按钮组件',
|
||||
icon: 'B',
|
||||
description: '创建点击按钮',
|
||||
componentName: 'ButtonComponent'
|
||||
},
|
||||
{
|
||||
id: 'form',
|
||||
type: 'form',
|
||||
name: '表单组件',
|
||||
icon: 'F',
|
||||
description: '收集用户输入数据',
|
||||
componentName: 'FormComponent'
|
||||
}
|
||||
]
|
||||
setTimeout(() => resolve(components), 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存装修页面
|
||||
* @param design 装修数据
|
||||
* @returns 保存结果
|
||||
*/
|
||||
export function saveDesign(design: DesignItem): Promise<{ id: string | number; message: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!design.name || design.name.trim() === '') {
|
||||
reject(new Error('装修名称不能为空'))
|
||||
return
|
||||
}
|
||||
if (!design.type) {
|
||||
reject(new Error('装修类型不能为空'))
|
||||
return
|
||||
}
|
||||
// TODO: 实际应调用后端API保存
|
||||
const result = {
|
||||
id: design.id || Math.random().toString(36).substr(2, 9),
|
||||
message: '保存成功'
|
||||
}
|
||||
setTimeout(() => resolve(result), 500)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布装修页面
|
||||
* @param designId 装修页面ID
|
||||
* @returns 发布结果
|
||||
*/
|
||||
export function publishDesign(designId: string | number): Promise<{ message: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!designId) {
|
||||
reject(new Error('装修ID不能为空'))
|
||||
return
|
||||
}
|
||||
// TODO: 实际应调用后端API发布
|
||||
setTimeout(() => {
|
||||
resolve({ message: '发布成功' })
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除装修页面
|
||||
* @param designId 装修页面ID
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export function deleteDesign(designId: string | number): Promise<{ message: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!designId) {
|
||||
reject(new Error('装修ID不能为空'))
|
||||
return
|
||||
}
|
||||
// TODO: 实际应调用后端API删除
|
||||
setTimeout(() => {
|
||||
resolve({ message: '删除成功' })
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取装修预览URL
|
||||
* @param designId 装修ID
|
||||
* @returns 预览URL
|
||||
*/
|
||||
export function getDesignPreviewUrl(designId: string | number): string {
|
||||
return `/pages/mall/design/preview/${designId}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取装修编辑URL
|
||||
* @param designId 装修ID
|
||||
* @returns 编辑URL
|
||||
*/
|
||||
export function getDesignEditorUrl(designId: string | number): string {
|
||||
return `/pages/mall/admin/design/editor?id=${designId}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
* @param dateStr 日期字符串
|
||||
* @returns 格式化后的日期
|
||||
*/
|
||||
export function formatDateTime(dateStr?: string): string {
|
||||
if (!dateStr) return '--'
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证组件配置
|
||||
* @param component 组件配置
|
||||
* @returns 验证结果
|
||||
*/
|
||||
export function validateComponent(component: DesignComponent): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = []
|
||||
|
||||
if (!component.id) {
|
||||
errors.push('组件ID不能为空')
|
||||
}
|
||||
|
||||
if (!component.type) {
|
||||
errors.push('组件类型不能为空')
|
||||
}
|
||||
|
||||
if (!component.name) {
|
||||
errors.push('组件名称不能为空')
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成组件ID
|
||||
* @param type 组件类型
|
||||
* @returns 生成的组件ID
|
||||
*/
|
||||
export function generateComponentId(type: string): string {
|
||||
const timestamp = Date.now().toString(36)
|
||||
const random = Math.random().toString(36).substr(2, 5)
|
||||
return `${type}-${timestamp}-${random}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取装修约束条件
|
||||
* @returns 约束条件对象
|
||||
*/
|
||||
export function getDesignConstraints(): DesignConstraints {
|
||||
return {
|
||||
maxComponents: 50,
|
||||
allowedComponentTypes: ['image', 'text', 'product', 'carousel', 'divider', 'spacer', 'button', 'form'],
|
||||
maxImageSize: 5242880, // 5MB
|
||||
supportedImageFormats: ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度克隆装修数据
|
||||
* @param design 装修数据
|
||||
* @returns 克隆后的数据
|
||||
*/
|
||||
export function cloneDesign(design: DesignItem): DesignItem {
|
||||
return JSON.parse(JSON.stringify(design))
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证装修数据完整性
|
||||
* @param design 装修数据
|
||||
* @returns 验证结果
|
||||
*/
|
||||
export function validateDesign(design: DesignItem): { valid: boolean; message: string } {
|
||||
if (!design.name || design.name.trim() === '') {
|
||||
return { valid: false, message: '装修名称不能为空' }
|
||||
}
|
||||
|
||||
if (!design.type) {
|
||||
return { valid: false, message: '装修类型不能为空' }
|
||||
}
|
||||
|
||||
if (!Array.isArray(design.content)) {
|
||||
return { valid: false, message: '装修内容格式错误' }
|
||||
}
|
||||
|
||||
if (design.content.length > getDesignConstraints().maxComponents) {
|
||||
return { valid: false, message: `组件数量超过限制(最多${getDesignConstraints().maxComponents}个)` }
|
||||
}
|
||||
|
||||
return { valid: true, message: '验证通过' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出装修为JSON
|
||||
* @param design 装修数据
|
||||
* @returns JSON字符串
|
||||
*/
|
||||
export function exportDesignJSON(design: DesignItem): string {
|
||||
return JSON.stringify(design, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON导入装修
|
||||
* @param jsonStr JSON字符串
|
||||
* @returns 装修数据
|
||||
*/
|
||||
export function importDesignJSON(jsonStr: string): DesignItem {
|
||||
try {
|
||||
return JSON.parse(jsonStr) as DesignItem
|
||||
} catch (error) {
|
||||
throw new Error('JSON格式错误,无法导入')
|
||||
}
|
||||
}
|
||||
25
pages/mall/admin/design/homepage.uvue
Normal file
25
pages/mall/admin/design/homepage.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('design-homepage')
|
||||
const title = ref<string>('首页装修')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
38
pages/mall/admin/design/link-management.uvue
Normal file
38
pages/mall/admin/design/link-management.uvue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<AdminLayout current-page="design-link">
|
||||
<view class="admin-main">
|
||||
<view class="header">
|
||||
<text class="title">链接管理</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text>商城链接管理(建设中)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-main {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
38
pages/mall/admin/design/material.uvue
Normal file
38
pages/mall/admin/design/material.uvue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<AdminLayout current-page="design-material">
|
||||
<view class="admin-main">
|
||||
<view class="header">
|
||||
<text class="title">素材管理</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text>商城素材管理(建设中)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-main {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
25
pages/mall/admin/design/product.uvue
Normal file
25
pages/mall/admin/design/product.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product')
|
||||
const title = ref<string>('product')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
162
pages/mall/admin/design/templates.uvue
Normal file
162
pages/mall/admin/design/templates.uvue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="design-container">
|
||||
<view class="module-header">
|
||||
<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">
|
||||
<view class="template-header">
|
||||
<text class="template-name">{{ template.name }}</text>
|
||||
</view>
|
||||
<view class="template-body">
|
||||
<text class="template-desc">{{ template.description }}</text>
|
||||
<button class="btn-use" @click="handleUseTemplate(template.id)">使用模板</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const currentPage = ref<string>('design-templates')
|
||||
|
||||
const templateLibrary = ref<any[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '首页模板A',
|
||||
description: '经典电商首页布局'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '首页模板B',
|
||||
description: '简约风格的商城页面'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '活动模板',
|
||||
description: '活动促销页面布局'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '商品模板',
|
||||
description: '商品展示页面布局'
|
||||
}
|
||||
])
|
||||
|
||||
const handleUseTemplate = (templateId: number) => {
|
||||
console.log('使用模板', templateId)
|
||||
uni.showToast({
|
||||
title: '使用模板成功',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
|
||||
.design-container {
|
||||
min-height: 100vh;
|
||||
background: $background-secondary;
|
||||
padding: $space-lg;
|
||||
}
|
||||
|
||||
.module-header {
|
||||
margin-bottom: $space-xl;
|
||||
}
|
||||
|
||||
.module-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
display: block;
|
||||
margin-bottom: $space-sm;
|
||||
}
|
||||
|
||||
.module-desc {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.templates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: $space-lg;
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: white;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-md;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.template-header {
|
||||
padding: $space-lg;
|
||||
background: $background-tertiary;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.template-body {
|
||||
padding: $space-lg;
|
||||
}
|
||||
|
||||
.template-name {
|
||||
font-size: $font-size-md;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
display: block;
|
||||
margin-bottom: $space-sm;
|
||||
}
|
||||
|
||||
.template-desc {
|
||||
color: $text-secondary;
|
||||
font-size: $font-size-sm;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
|
||||
.btn-use {
|
||||
background: $primary-color;
|
||||
color: white;
|
||||
padding: $space-sm $space-lg;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
margin-top: $space-lg;
|
||||
font-size: $font-size-sm;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.design-container {
|
||||
padding: $space-md;
|
||||
}
|
||||
|
||||
.templates-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.templates-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
38
pages/mall/admin/design/theme-style.uvue
Normal file
38
pages/mall/admin/design/theme-style.uvue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<AdminLayout current-page="design-theme">
|
||||
<view class="admin-main">
|
||||
<view class="header">
|
||||
<text class="title">主题风格</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text>商城主题风格设置(建设中)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-main {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
38
pages/mall/admin/design/user.uvue
Normal file
38
pages/mall/admin/design/user.uvue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<AdminLayout current-page="design-user">
|
||||
<view class="admin-main">
|
||||
<view class="header">
|
||||
<text class="title">个人中心装修</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text>个人中心页面装修(建设中)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-main {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/finance/record.uvue
Normal file
81
pages/mall/admin/finance/record.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 财务记录 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,531 +1,24 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="home">
|
||||
<view class="dashboard-page">
|
||||
<!-- 第一行:4 个 KPI 卡片 -->
|
||||
<view class="kpi-cards-row">
|
||||
<KpiMiniCard
|
||||
title="销售额"
|
||||
tagText="今日"
|
||||
valuePrefix="¥"
|
||||
:valueText="String(formatNumber(kpiData.sales.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.sales.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.sales.change)}%`"
|
||||
:trend="kpiData.sales.change > 0 ? 'up' : (kpiData.sales.change < 0 ? 'down' : 'flat')"
|
||||
:footerLeftText="'本月销售额'"
|
||||
:footerRightText="`¥${formatNumber(kpiData.sales.monthTotal)}`"
|
||||
/>
|
||||
|
||||
<KpiMiniCard
|
||||
title="用户访问量"
|
||||
tagText="今日"
|
||||
:valueText="String(formatNumber(kpiData.visits.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.visits.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.visits.change)}%`"
|
||||
:trend="kpiData.visits.change > 0 ? 'up' : (kpiData.visits.change < 0 ? 'down' : 'flat')"
|
||||
footerLeftText="本月访问量"
|
||||
:footerRightText="`${formatNumber(kpiData.visits.monthTotal)}Pv`"
|
||||
/>
|
||||
|
||||
<KpiMiniCard
|
||||
title="订单量"
|
||||
tagText="今日"
|
||||
:valueText="String(formatNumber(kpiData.orders.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.orders.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.orders.change)}%`"
|
||||
:trend="kpiData.orders.change > 0 ? 'up' : (kpiData.orders.change < 0 ? 'down' : 'flat')"
|
||||
footerLeftText="本月订单量"
|
||||
:footerRightText="`${formatNumber(kpiData.orders.monthTotal)}单`"
|
||||
/>
|
||||
|
||||
<KpiMiniCard
|
||||
title="新增用户"
|
||||
tagText="今日"
|
||||
:valueText="String(formatNumber(kpiData.users.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.users.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.users.change)}%`"
|
||||
:trend="kpiData.users.change > 0 ? 'up' : (kpiData.users.change < 0 ? 'down' : 'flat')"
|
||||
footerLeftText="本月新增用户"
|
||||
:footerRightText="`${formatNumber(kpiData.users.monthTotal)}人`"
|
||||
/>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 第二行:订单统计图表 -->
|
||||
<view class="chart-section">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<view class="header-left">
|
||||
<view class="title-icon">
|
||||
<!-- 不用 emoji,纯样式画一个“图表感”的小方块 -->
|
||||
<view class="title-icon-mark"></view>
|
||||
</view>
|
||||
<text class="admin-card-title">订单</text>
|
||||
</view>
|
||||
|
||||
<view class="chart-controls">
|
||||
<view
|
||||
v-for="p in chartPeriods"
|
||||
:key="p.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: selectedPeriod === p.value }"
|
||||
@click="changePeriod(p.value)"
|
||||
>
|
||||
<text class="seg-btn-text">{{ p.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="admin-card-body">
|
||||
<!-- 图表容器:你后面接 ECharts / uCharts 都挂这里 -->
|
||||
<view class="echarts-container">
|
||||
<!-- 先空着也行;不要放 emoji 占位符 -->
|
||||
111
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第三行:用户统计图表 -->
|
||||
<view class="charts-row">
|
||||
<!-- 用户趋势折线图 -->
|
||||
<view class="chart-col">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户趋势</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">📈 ECharts 折线图:用户增长趋势</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户构成饼图 -->
|
||||
<view class="chart-col">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户构成</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">🥧 ECharts 饼图:用户来源分布</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
<!-- 管理后台入口:直接加载 AdminLayout,使用 CRMEB 内部路由系统 -->
|
||||
<AdminLayout />
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
/**
|
||||
* 管理后台入口页面
|
||||
*
|
||||
* 架构说明:
|
||||
* 1. 此页面是 pages.json 中配置的主入口
|
||||
* 2. 直接加载 AdminLayout 组件作为容器
|
||||
* 3. AdminLayout 内部使用 CRMEB 路由系统管理所有子页面
|
||||
* 4. 不需要额外的业务逻辑,保持简洁
|
||||
*
|
||||
* 路由流程:
|
||||
* pages.json → homePage/index.uvue → AdminLayout → 内部路由切换
|
||||
*/
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
import KpiMiniCard from './components/KpiMiniCard.uvue'
|
||||
|
||||
// KPI 数据
|
||||
const kpiData = ref({
|
||||
sales: {
|
||||
today: 125680.50,
|
||||
yesterday: 118920.30,
|
||||
monthTotal: 2857808.90,
|
||||
change: 5.7
|
||||
},
|
||||
visits: {
|
||||
today: 15420,
|
||||
yesterday: 14890,
|
||||
monthTotal: 342680,
|
||||
change: 3.4
|
||||
},
|
||||
orders: {
|
||||
today: 342,
|
||||
yesterday: 318,
|
||||
monthTotal: 8956,
|
||||
change: 7.5
|
||||
},
|
||||
users: {
|
||||
today: 156,
|
||||
yesterday: 142,
|
||||
monthTotal: 3245,
|
||||
change: 9.9
|
||||
}
|
||||
})
|
||||
|
||||
// 图表配置
|
||||
const selectedPeriod = ref('30days')
|
||||
const selectedPeriodLabel = computed((): string => {
|
||||
const hit = chartPeriods.value.find((x) => x.value === selectedPeriod.value)
|
||||
return hit ? hit.label : ""
|
||||
})
|
||||
|
||||
const chartPeriods = [
|
||||
{ label: '30天', value: '30days' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '年', value: 'year' }
|
||||
]
|
||||
|
||||
type PeriodItem = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
// 方法
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'k'
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
const changePeriod = (period: string) => {
|
||||
selectedPeriod.value = period
|
||||
const periodMap: Record<string, string> = {
|
||||
'30days': '30天',
|
||||
'week': '周',
|
||||
'month': '月',
|
||||
'year': '年'
|
||||
}
|
||||
selectedPeriodLabel.value = periodMap[period] || '30天'
|
||||
|
||||
// TODO: 重新加载图表数据
|
||||
console.log('切换时间粒度:', period)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ===== Dashboard 页面样式 ===== */
|
||||
.dashboard-page {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===== KPI 卡片行 ===== */
|
||||
/* 第一行:4 个 KPI 卡片一行 */
|
||||
.kpi-cards-row{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)); /* 一行 4 列等分 */
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* 卡片本体:不要写死宽高 */
|
||||
.kpi-card{
|
||||
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 200px; /* 你可以改成 140/160,别写死 200px */
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20rpx;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
/* 响应式:宽度不够时变 2 列 / 1 列(可选) */
|
||||
@media (max-width: 1200px){
|
||||
.kpi-cards-row{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 768px){
|
||||
.kpi-cards-row{ grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
|
||||
.kpi-card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.kpi-card-title {
|
||||
position: absolute;
|
||||
|
||||
top: 10rpx;
|
||||
left: 5rpx;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.kpi-card-tag {
|
||||
background-color: #1890ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.kpi-tag-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.kpi-card-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-number {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
border-radius: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.kpi-value-trend.up {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.kpi-value-trend.down {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.kpi-trend-text {
|
||||
margin-left: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kpi-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.kpi-footer-text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.kpi-card-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 图表区域 ===== */
|
||||
|
||||
|
||||
/* 卡片外观 */
|
||||
.admin-card {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 头部:左标题 + 右分段按钮(不换行) */
|
||||
.admin-card-header {
|
||||
padding: 16px 24px 12px 24px;
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap; /* 防止被挤下去 */
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
background: #e6f4ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-icon-mark {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.admin-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 分段控件:一整条外框 + 内部分段(完全贴近你第二张图右上角) */
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
align-items: center;
|
||||
justify-content: center;;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
flex-shrink: 0; /* 防止被压缩换行 */
|
||||
}
|
||||
|
||||
.seg-btn {
|
||||
height: 32px;
|
||||
min-width: 44px;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.seg-btn:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.seg-btn-text {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.seg-btn.active {
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.seg-btn.active .seg-btn-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ✅ 注意:body 是 header 的兄弟,不要写进 header 嵌套里 */
|
||||
.admin-card-body {
|
||||
padding: 0 24px 16px 24px;
|
||||
}
|
||||
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 300px; /* 贴近截图比例 */
|
||||
}
|
||||
|
||||
.charts-row{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
/* 每个图表列容器 */
|
||||
.chart-col{
|
||||
min-width: 0; /* 防止 ECharts/SVG 内容把列撑爆 */
|
||||
}
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
|
||||
.kpi-card {
|
||||
min-width: 45%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.charts-row{
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.kpi-cards-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.dashboard-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.kpi-cards-row,
|
||||
.chart-section,
|
||||
.charts-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.admin-card-header,
|
||||
.admin-card-body {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 图标字体 ===== */
|
||||
.iconfont {
|
||||
font-family: 'iconfont';
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-up:before {
|
||||
content: '↑';
|
||||
}
|
||||
|
||||
.icon-down:before {
|
||||
content: '↓';
|
||||
}
|
||||
|
||||
.icon-sales:before {
|
||||
content: '💰';
|
||||
}
|
||||
|
||||
.icon-visits:before {
|
||||
content: '👁️';
|
||||
}
|
||||
|
||||
.icon-orders:before {
|
||||
content: '📦';
|
||||
}
|
||||
|
||||
.icon-users:before {
|
||||
content: '👥';
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
/* 无需额外样式,完全由 AdminLayout 控制布局 */
|
||||
</style>
|
||||
|
||||
483
pages/mall/admin/homePage/index.uvue.bak
Normal file
483
pages/mall/admin/homePage/index.uvue.bak
Normal file
@@ -0,0 +1,483 @@
|
||||
<template>
|
||||
<!-- 直接加载 AdminLayout,使用 CRMEB 内部路由系统 -->
|
||||
<AdminLayout />
|
||||
|
||||
|
||||
<!-- 第二行:订单统计图表 -->
|
||||
<view class="chart-section">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<view class="header-left">
|
||||
<view class="title-icon">
|
||||
<!-- 不用 emoji,纯样式画一个“图表感”的小方块 -->
|
||||
<view class="title-icon-mark"></view>
|
||||
</view>
|
||||
<text class="admin-card-title">订单</text>
|
||||
</view>
|
||||
|
||||
<view class="chart-controls">
|
||||
<view
|
||||
v-for="p in chartPeriods"
|
||||
:key="p.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: selectedPeriod === p.value }"
|
||||
@click="changePeriod(p.value)"
|
||||
>
|
||||
<text class="seg-btn-text">{{ p.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="admin-card-body">
|
||||
<!-- 图表容器:你后面接 ECharts / uCharts 都挂这里 -->
|
||||
<view class="echarts-container">
|
||||
<!-- 先空着也行;不要放 emoji 占位符 -->
|
||||
111
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第三行:用户统计图表 -->
|
||||
<view class="charts-row">
|
||||
<!-- 用户趋势折线图 -->
|
||||
<view class="chart-col">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户趋势</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">📈 ECharts 折线图:用户增长趋势</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户构成饼图 -->
|
||||
<view class="chart-col">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户构成</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">🥧 ECharts 饼图:用户来源分布</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import KpiMiniCard from './components/KpiMiniCard.uvue'
|
||||
|
||||
// KPI 数据
|
||||
const kpiData = ref({
|
||||
sales: {
|
||||
today: 125680.50,
|
||||
yesterday: 118920.30,
|
||||
monthTotal: 2857808.90,
|
||||
change: 5.7
|
||||
},
|
||||
visits: {
|
||||
today: 15420,
|
||||
yesterday: 14890,
|
||||
monthTotal: 342680,
|
||||
change: 3.4
|
||||
},
|
||||
orders: {
|
||||
today: 342,
|
||||
yesterday: 318,
|
||||
monthTotal: 8956,
|
||||
change: 7.5
|
||||
},
|
||||
users: {
|
||||
today: 156,
|
||||
yesterday: 142,
|
||||
monthTotal: 3245,
|
||||
change: 9.9
|
||||
}
|
||||
})
|
||||
|
||||
// 图表配置
|
||||
const selectedPeriod = ref('30days')
|
||||
const selectedPeriodLabel = computed((): string => {
|
||||
const hit = chartPeriods.value.find((x) => x.value === selectedPeriod.value)
|
||||
return hit ? hit.label : ""
|
||||
})
|
||||
|
||||
const chartPeriods = [
|
||||
{ label: '30天', value: '30days' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '年', value: 'year' }
|
||||
]
|
||||
|
||||
type PeriodItem = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
// 方法
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'k'
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
const changePeriod = (period: string) => {
|
||||
selectedPeriod.value = period
|
||||
const periodMap: Record<string, string> = {
|
||||
'30days': '30天',
|
||||
'week': '周',
|
||||
'month': '月',
|
||||
'year': '年'
|
||||
}
|
||||
selectedPeriodLabel.value = periodMap[period] || '30天'
|
||||
|
||||
// TODO: 重新加载图表数据
|
||||
console.log('切换时间粒度:', period)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ===== Dashboard 页面样式 ===== */
|
||||
.dashboard-page {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===== KPI 卡片行 ===== */
|
||||
/* 第一行:4 个 KPI 卡片一行 */
|
||||
.kpi-cards-row{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)); /* 一行 4 列等分 */
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* 卡片本体:不要写死宽高 */
|
||||
.kpi-card{
|
||||
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 200px; /* 你可以改成 140/160,别写死 200px */
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20rpx;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
/* 响应式:宽度不够时变 2 列 / 1 列(可选) */
|
||||
@media (max-width: 1200px){
|
||||
.kpi-cards-row{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 768px){
|
||||
.kpi-cards-row{ grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
|
||||
.kpi-card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.kpi-card-title {
|
||||
position: absolute;
|
||||
|
||||
top: 10rpx;
|
||||
left: 5rpx;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.kpi-card-tag {
|
||||
background-color: #1890ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.kpi-tag-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.kpi-card-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-number {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
border-radius: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.kpi-value-trend.up {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.kpi-value-trend.down {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.kpi-trend-text {
|
||||
margin-left: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kpi-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.kpi-footer-text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.kpi-card-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 图表区域 ===== */
|
||||
|
||||
|
||||
/* 卡片外观 */
|
||||
.admin-card {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 头部:左标题 + 右分段按钮(不换行) */
|
||||
.admin-card-header {
|
||||
padding: 16px 24px 12px 24px;
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap; /* 防止被挤下去 */
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
background: #e6f4ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-icon-mark {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.admin-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 分段控件:一整条外框 + 内部分段(完全贴近你第二张图右上角) */
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
align-items: center;
|
||||
justify-content: center;;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
flex-shrink: 0; /* 防止被压缩换行 */
|
||||
}
|
||||
|
||||
.seg-btn {
|
||||
height: 32px;
|
||||
min-width: 44px;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.seg-btn:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.seg-btn-text {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.seg-btn.active {
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.seg-btn.active .seg-btn-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ✅ 注意:body 是 header 的兄弟,不要写进 header 嵌套里 */
|
||||
.admin-card-body {
|
||||
padding: 0 24px 16px 24px;
|
||||
}
|
||||
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 300px; /* 贴近截图比例 */
|
||||
}
|
||||
|
||||
.charts-row{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
/* 每个图表列容器 */
|
||||
.chart-col{
|
||||
min-width: 0; /* 防止 ECharts/SVG 内容把列撑爆 */
|
||||
}
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
|
||||
.kpi-card {
|
||||
min-width: 45%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.charts-row{
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.kpi-cards-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.dashboard-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.kpi-cards-row,
|
||||
.chart-section,
|
||||
.charts-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.admin-card-header,
|
||||
.admin-card-body {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 图标字体 ===== */
|
||||
.iconfont {
|
||||
font-family: 'iconfont';
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-up:before {
|
||||
content: '↑';
|
||||
}
|
||||
|
||||
.icon-down:before {
|
||||
content: '↓';
|
||||
}
|
||||
|
||||
.icon-sales:before {
|
||||
content: '💰';
|
||||
}
|
||||
|
||||
.icon-visits:before {
|
||||
content: '👁️';
|
||||
}
|
||||
|
||||
.icon-orders:before {
|
||||
content: '📦';
|
||||
}
|
||||
|
||||
.icon-users:before {
|
||||
content: '👥';
|
||||
}
|
||||
</style>
|
||||
13
pages/mall/admin/index_new.uvue
Normal file
13
pages/mall/admin/index_new.uvue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<AdminLayout />
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
// AdminLayout 现在采用内部路由模式
|
||||
// 所有页面内容在 AdminLayout 内部切换,不再使用 uni.navigateTo
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="dev-tools-codegen">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">代码生成</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text class="tip">TODO: 代码生成</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<view class="header">
|
||||
<text class="title">代码生成</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text class="tip">TODO: 代码生成</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
81
pages/mall/admin/marketing/bargain/list.uvue
Normal file
81
pages/mall/admin/marketing/bargain/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 砍价活动 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/marketing/combination/list.uvue
Normal file
81
pages/mall/admin/marketing/combination/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">拼团活动</text>
|
||||
<text class="page-subtitle">Component: MarketingCombination</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 拼团活动 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
27
pages/mall/admin/marketing/groupbuy/goods.uvue
Normal file
27
pages/mall/admin/marketing/groupbuy/goods.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('groupbuy-goods')
|
||||
const title = ref<string>('goods')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/groupbuy/list.uvue
Normal file
27
pages/mall/admin/marketing/groupbuy/list.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('groupbuy-list')
|
||||
const title = ref<string>('list')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
26
pages/mall/admin/marketing/index.uvue
Normal file
26
pages/mall/admin/marketing/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('marketing')
|
||||
const title = ref<string>('营销看板')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
81
pages/mall/admin/marketing/integral/list.uvue
Normal file
81
pages/mall/admin/marketing/integral/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">积分管理</text>
|
||||
<text class="page-subtitle">Component: MarketingIntegral</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 积分管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
27
pages/mall/admin/marketing/live/anchor.uvue
Normal file
27
pages/mall/admin/marketing/live/anchor.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('live-anchor')
|
||||
const title = ref<string>('anchor')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/live/goods.uvue
Normal file
27
pages/mall/admin/marketing/live/goods.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('live-goods')
|
||||
const title = ref<string>('goods')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/live/room.uvue
Normal file
27
pages/mall/admin/marketing/live/room.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('live-room')
|
||||
const title = ref<string>('room')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/lottery/config.uvue
Normal file
27
pages/mall/admin/marketing/lottery/config.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('lottery-config')
|
||||
const title = ref<string>('config')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/lottery/list.uvue
Normal file
27
pages/mall/admin/marketing/lottery/list.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('lottery-list')
|
||||
const title = ref<string>('list')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/member/card.uvue
Normal file
27
pages/mall/admin/marketing/member/card.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('member-card')
|
||||
const title = ref<string>('card')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/member/config.uvue
Normal file
27
pages/mall/admin/marketing/member/config.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('member-config')
|
||||
const title = ref<string>('config')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/member/record.uvue
Normal file
27
pages/mall/admin/marketing/member/record.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('member-record')
|
||||
const title = ref<string>('record')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/member/rights.uvue
Normal file
27
pages/mall/admin/marketing/member/rights.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('member-rights')
|
||||
const title = ref<string>('rights')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/member/type.uvue
Normal file
27
pages/mall/admin/marketing/member/type.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('member-type')
|
||||
const title = ref<string>('type')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/newcomer.uvue
Normal file
27
pages/mall/admin/marketing/newcomer.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('newcomer')
|
||||
const title = ref<string>('newcomer')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/points/config.uvue
Normal file
27
pages/mall/admin/marketing/points/config.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('points-config')
|
||||
const title = ref<string>('config')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
25
pages/mall/admin/marketing/points/goods.uvue
Normal file
25
pages/mall/admin/marketing/points/goods.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('goods')
|
||||
const title = ref<string>('goods')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/marketing/points/order.uvue
Normal file
25
pages/mall/admin/marketing/points/order.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order')
|
||||
const title = ref<string>('order')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
27
pages/mall/admin/marketing/points/record.uvue
Normal file
27
pages/mall/admin/marketing/points/record.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('points-record')
|
||||
const title = ref<string>('record')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/points/stats.uvue
Normal file
27
pages/mall/admin/marketing/points/stats.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">积分统计数据,包含积分发放和消费情况?</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('points-stats')
|
||||
const title = ref<string>('积分统计')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/recharge/amount.uvue
Normal file
27
pages/mall/admin/marketing/recharge/amount.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('recharge-amount')
|
||||
const title = ref<string>('amount')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/recharge/config.uvue
Normal file
27
pages/mall/admin/marketing/recharge/config.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('recharge-config')
|
||||
const title = ref<string>('config')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/recharge/record.uvue
Normal file
27
pages/mall/admin/marketing/recharge/record.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('recharge-record')
|
||||
const title = ref<string>('record')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/seckill/config.uvue
Normal file
27
pages/mall/admin/marketing/seckill/config.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('seckill-config')
|
||||
const title = ref<string>('config')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/marketing/seckill/goods.uvue
Normal file
27
pages/mall/admin/marketing/seckill/goods.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('seckill-goods')
|
||||
const title = ref<string>('goods')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
87
pages/mall/admin/marketing/seckill/list.uvue
Normal file
87
pages/mall/admin/marketing/seckill/list.uvue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">秒杀列表</text>
|
||||
<text class="page-subtitle">Component: MarketingSeckill</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const pageTitle = ref<string>('秒杀列表')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #ffffff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background-color: #ffffff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="order-management">
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="Page">
|
||||
<view class="Header">
|
||||
<text class="Title">订单</text>
|
||||
@@ -19,10 +19,16 @@ import { onLoad } from '@dcloudio/uni-app'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const params = ref('')
|
||||
const currentPage = ref('order-list')
|
||||
|
||||
onLoad((options) => {
|
||||
// options: Record<string, any>
|
||||
onLoad((options: Record<string, string>) => {
|
||||
params.value = JSON.stringify(options ?? {})
|
||||
const tab = options['tab'] || ''
|
||||
if (tab == 'stats') currentPage.value = 'order-stats'
|
||||
else if (tab == 'aftersale') currentPage.value = 'order-aftersale'
|
||||
else if (tab == 'cashier') currentPage.value = 'order-cashier'
|
||||
else if (tab == 'verify') currentPage.value = 'order-verify'
|
||||
else if (tab == 'config') currentPage.value = 'order-config'
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
26
pages/mall/admin/order/aftersales-order/index.uvue
Normal file
26
pages/mall/admin/order/aftersales-order/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">售后订单管理与申请处理</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-aftersale')
|
||||
const title = ref<string>('售后订单')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
26
pages/mall/admin/order/cashier-order/index.uvue
Normal file
26
pages/mall/admin/order/cashier-order/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">收银台订单,管理线下收银记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-cashier')
|
||||
const title = ref<string>('收银订单')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
81
pages/mall/admin/order/list.uvue
Normal file
81
pages/mall/admin/order/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">订单管理</text>
|
||||
<text class="page-subtitle">Component: OrderList</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 订单管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
26
pages/mall/admin/order/order-configuration/index.uvue
Normal file
26
pages/mall/admin/order/order-configuration/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">订单配置,设置订单相关参数</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-config')
|
||||
const title = ref<string>('订单配置')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
25
pages/mall/admin/order/order-management/index.uvue
Normal file
25
pages/mall/admin/order/order-management/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-list')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
26
pages/mall/admin/order/order-statistics/index.uvue
Normal file
26
pages/mall/admin/order/order-statistics/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">订单交易统计数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-statistics')
|
||||
const title = ref<string>('订单统计')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
26
pages/mall/admin/order/write-off-records/index.uvue
Normal file
26
pages/mall/admin/order/write-off-records/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">核销记录,查看优惠券和卡券核销情况</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('order-verify')
|
||||
const title = ref<string>('核销记录')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
81
pages/mall/admin/product/attr.uvue
Normal file
81
pages/mall/admin/product/attr.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品规格</text>
|
||||
<text class="page-subtitle">Component: ProductAttr</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品规格 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/product/classify.uvue
Normal file
81
pages/mall/admin/product/classify.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品分类</text>
|
||||
<text class="page-subtitle">Component: ProductClassify</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品分类 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/product/label.uvue
Normal file
81
pages/mall/admin/product/label.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品标签</text>
|
||||
<text class="page-subtitle">Component: ProductLabel</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品标签 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/product/list.uvue
Normal file
81
pages/mall/admin/product/list.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/product/param.uvue
Normal file
81
pages/mall/admin/product/param.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品参数</text>
|
||||
<text class="page-subtitle">Component: ProductParam</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品参数 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
25
pages/mall/admin/product/product-classification/index.uvue
Normal file
25
pages/mall/admin/product/product-classification/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('index')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/product/product-label/index.uvue
Normal file
25
pages/mall/admin/product/product-label/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('index')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/product/product-management/index.uvue
Normal file
25
pages/mall/admin/product/product-management/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product-list')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/product/product-param/index.uvue
Normal file
25
pages/mall/admin/product/product-param/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('index')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
26
pages/mall/admin/product/product-protection/index.uvue
Normal file
26
pages/mall/admin/product/product-protection/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">商品保障条款℃</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product-protection')
|
||||
const title = ref<string>('商品保障')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
25
pages/mall/admin/product/product-reviews/index.uvue
Normal file
25
pages/mall/admin/product/product-reviews/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('index')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
26
pages/mall/admin/product/product-specifications/index.uvue
Normal file
26
pages/mall/admin/product/product-specifications/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">商品规格配置</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product-specifications')
|
||||
const title = ref<string>('商品规格')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
26
pages/mall/admin/product/product-statistics/index.uvue
Normal file
26
pages/mall/admin/product/product-statistics/index.uvue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">分析商品销售数据和趋势</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product-statistics-group')
|
||||
const title = ref<string>('商品统计')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
81
pages/mall/admin/product/protection.uvue
Normal file
81
pages/mall/admin/product/protection.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品保障</text>
|
||||
<text class="page-subtitle">Component: ProductProtection</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品保障 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/product/reply.uvue
Normal file
81
pages/mall/admin/product/reply.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品评论</text>
|
||||
<text class="page-subtitle">Component: ProductReply</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品评论 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
561
pages/mall/admin/service/autoReply.uvue
Normal file
561
pages/mall/admin/service/autoReply.uvue
Normal file
@@ -0,0 +1,561 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="service-autoReply">
|
||||
<view class="service-container">
|
||||
<view class="content-card">
|
||||
<view class="header-row">
|
||||
<view class="header-title">自动回复管理</view>
|
||||
<view class="header-actions">
|
||||
<button class="btn-primary" @click="handleCreate">新增自动回复</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">关键词</text>
|
||||
<input v-model="searchKeyword" class="filter-input" placeholder="请输入关键词" />
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">状态</text>
|
||||
<picker v-model="filterStatus" class="picker-wrapper" :range="statusOptions" range-key="label">
|
||||
<view>{{ filterStatus > -1 ? statusOptions[filterStatus].label : '全部' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<button class="btn-search" @click="handleSearch">搜索</button>
|
||||
<button class="btn-reset" @click="handleReset">重置</button>
|
||||
</view>
|
||||
|
||||
<view v-if="selectedIds.length > 0" class="batch-actions">
|
||||
<text class="batch-text">已选择 {{ selectedIds.length }} 条</text>
|
||||
<button class="btn-batch-delete" @click="handleBatchDelete">批量删除</button>
|
||||
<button class="btn-cancel-select" @click="handleCancelSelect">取消选择</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="table">
|
||||
<view class="table-header">
|
||||
<view class="cell cell-checkbox"><input type="checkbox" :checked="selectAll" @change="handleSelectAll"></view>
|
||||
<view class="cell cell-id">ID</view>
|
||||
<view class="cell cell-keyword">关键词</view>
|
||||
<view class="cell cell-reply">回复内容</view>
|
||||
<view class="cell cell-status">状态</view>
|
||||
<view class="cell cell-time">更新时间</view>
|
||||
<view class="cell cell-actions">操作</view>
|
||||
</view>
|
||||
<view v-if="list.length === 0" class="empty">暂无数据</view>
|
||||
<view v-for="item in list" :key="item.id" class="table-row" :class="{ selected: selectedIds.includes(item.id) }">
|
||||
<view class="cell cell-checkbox"><input type="checkbox" :checked="selectedIds.includes(item.id)" @change="() => handleSelectItem(item.id)"></view>
|
||||
<view class="cell cell-id">{{ item.id }}</view>
|
||||
<view class="cell cell-keyword">{{ item.keyword }}</view>
|
||||
<view class="cell cell-reply">{{ item.reply }}</view>
|
||||
<view class="cell cell-status">
|
||||
<text class="badge" :class="{ on: item.status === 1 }">{{ item.status === 1 ? '启用' : '禁用' }}</text>
|
||||
</view>
|
||||
<view class="cell cell-time">{{ item.updated_at }}</view>
|
||||
<view class="cell cell-actions">
|
||||
<button class="btn-action" @click="handleEdit(item.id)">编辑</button>
|
||||
<button class="btn-action btn-danger" @click="handleDelete(item.id)">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pagination">
|
||||
<button class="btn-page" :disabled="page === 1" @click="prevPage">上一页</button>
|
||||
<text class="page-text">第 {{ page }} 页</text>
|
||||
<button class="btn-page" :disabled="page >= totalPage" @click="nextPage">下一页</button>
|
||||
</view>
|
||||
|
||||
<!-- 编辑/新增自动回复 -->
|
||||
<view v-if="showModal" class="modal-overlay" @click.self="closeModal">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">{{ editingId ? '编辑自动回复' : '新增自动回复' }}</text>
|
||||
<button class="modal-close" @click="closeModal">关闭</button>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-group">
|
||||
<label class="form-label">关键词<text class="required">*</text></label>
|
||||
<input v-model="form.keyword" class="form-input" placeholder="请输入关键词" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<label class="form-label">回复内容 <text class="required">*</text></label>
|
||||
<textarea v-model="form.reply" class="form-textarea" placeholder="请输入回复内容"></textarea>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<label class="form-label">状态</label>
|
||||
<picker v-model="form.status" class="picker-wrapper" :range="statusOptions" range-key="label">
|
||||
<view>{{ statusOptions[form.status].label }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button class="btn-cancel" @click="closeModal">取消</button>
|
||||
<button class="btn-confirm" @click="handleSave">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
import { getAutoReplyList, saveAutoReply, deleteAutoReply } from './service.uts'
|
||||
|
||||
const list = ref<any[]>([])
|
||||
const page = ref<number>(1)
|
||||
const pageSize = 10
|
||||
const total = ref<number>(0)
|
||||
|
||||
const selectedIds = ref<number[]>([])
|
||||
const selectAll = ref<boolean>(false)
|
||||
const showModal = ref<boolean>(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const searchKeyword = ref<string>('')
|
||||
const filterStatus = ref<number>(-1)
|
||||
|
||||
const form = ref<any>({
|
||||
keyword: '',
|
||||
reply: '',
|
||||
status: 1
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 }
|
||||
]
|
||||
|
||||
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize)))
|
||||
|
||||
const loadList = async () => {
|
||||
const res = await getAutoReplyList({
|
||||
page: page.value,
|
||||
limit: pageSize,
|
||||
keyword: searchKeyword.value,
|
||||
status: filterStatus.value > -1 ? filterStatus.value : undefined
|
||||
})
|
||||
list.value = res.items
|
||||
total.value = res.total
|
||||
selectedIds.value = []
|
||||
selectAll.value = false
|
||||
}
|
||||
|
||||
const handleSelectAll = () => {
|
||||
selectAll.value = !selectAll.value
|
||||
if (selectAll.value) {
|
||||
selectedIds.value = list.value.map(item => item.id)
|
||||
} else {
|
||||
selectedIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectItem = (id: number) => {
|
||||
const index = selectedIds.value.indexOf(id)
|
||||
if (index > -1) {
|
||||
selectedIds.value.splice(index, 1)
|
||||
} else {
|
||||
selectedIds.value.push(id)
|
||||
}
|
||||
selectAll.value = selectedIds.value.length === list.value.length && list.value.length > 0
|
||||
}
|
||||
|
||||
const handleCancelSelect = () => {
|
||||
selectedIds.value = []
|
||||
selectAll.value = false
|
||||
}
|
||||
|
||||
const handleCreate = () => {
|
||||
editingId.value = null
|
||||
form.value = { keyword: '', reply: '', status: 1 }
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (id: number) => {
|
||||
const item = list.value.find(i => i.id === id)
|
||||
if (item) {
|
||||
editingId.value = id
|
||||
form.value = { keyword: item.keyword, reply: item.reply, status: item.status }
|
||||
showModal.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.value.keyword.trim()) {
|
||||
uni.showToast({ title: '请输入关键词', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!form.value.reply.trim()) {
|
||||
uni.showToast({ title: '请输入回复内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
await saveAutoReply({
|
||||
id: editingId.value,
|
||||
...form.value
|
||||
})
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
closeModal()
|
||||
loadList()
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
showModal.value = false
|
||||
editingId.value = null
|
||||
form.value = { keyword: '', reply: '', status: 1 }
|
||||
}
|
||||
|
||||
const handleBatchDelete = () => {
|
||||
uni.showModal({
|
||||
title: '批量删除',
|
||||
content: `确认删除选中的 ${selectedIds.value.length} 条自动回复吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
for (const id of selectedIds.value) {
|
||||
await deleteAutoReply(id)
|
||||
}
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
uni.showModal({
|
||||
title: '删除自动回复',
|
||||
content: '确认删除该自动回复吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await deleteAutoReply(id)
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
page.value = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchKeyword.value = ''
|
||||
filterStatus.value = -1
|
||||
page.value = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const prevPage = () => {
|
||||
if (page.value > 1) {
|
||||
page.value--
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (page.value < totalPage.value) {
|
||||
page.value++
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
|
||||
.service-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
padding: $space-lg;
|
||||
background: $background-secondary;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
padding: $space-lg;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
.header-title { font-size: $font-size-lg; font-weight: 600; color: $text-primary; }
|
||||
|
||||
.btn-primary { background: $primary-color; color: #fff; padding: $space-sm $space-lg; border-radius: $radius-sm; border: none; font-size: $font-size-sm; }
|
||||
|
||||
.filter-section {
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: $space-md;
|
||||
margin-bottom: $space-lg;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: $space-sm;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
color: $text-secondary;
|
||||
font-size: $font-size-sm;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.picker-wrapper {
|
||||
padding: $space-xs $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
padding: $space-xs $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
background: $primary-color;
|
||||
color: #fff;
|
||||
padding: $space-xs $space-lg;
|
||||
border: none;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: transparent;
|
||||
color: $primary-color;
|
||||
border: 1px solid $primary-color;
|
||||
padding: $space-xs $space-lg;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: $space-md;
|
||||
padding: $space-md;
|
||||
background: #fef3c7;
|
||||
border-radius: $radius-sm;
|
||||
margin-top: $space-md;
|
||||
}
|
||||
|
||||
.batch-text {
|
||||
font-size: $font-size-sm;
|
||||
color: #92400e;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.btn-batch-delete,
|
||||
.btn-cancel-select {
|
||||
padding: $space-xs $space-md;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
font-size: $font-size-xs;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-batch-delete {
|
||||
background: $error-color;
|
||||
}
|
||||
|
||||
.btn-cancel-select {
|
||||
background: #d1d5db;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.table { border: 1px solid $border-color; border-radius: $radius-sm; overflow: hidden; }
|
||||
.table-header,
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.table-header { background: $background-tertiary; font-weight: 600; }
|
||||
.table-row { border-top: 1px solid $border-color; }
|
||||
.table-row.selected { background: #f3f4f6; }
|
||||
|
||||
.cell { padding: $space-sm $space-md; font-size: $font-size-sm; color: $text-primary; display: flex; align-items: center; }
|
||||
.cell-checkbox { width: 50px; justify-content: center; }
|
||||
.cell-id { width: 60px; justify-content: center; }
|
||||
.cell-keyword { width: 140px; }
|
||||
.cell-reply {
|
||||
flex: 1;
|
||||
flex-direction:row
|
||||
}
|
||||
.cell-status { width: 100px; justify-content: center; }
|
||||
.cell-time { width: 180px; }
|
||||
.cell-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
width: 180px;
|
||||
gap: $space-sm;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.badge { padding: 2px 10px; border-radius: $radius-sm; background: lighten($error-color, 40%); color: $error-color; font-size: $font-size-xs; }
|
||||
.badge.on { background: lighten($success-color, 40%); color: $success-color; }
|
||||
|
||||
.btn-action { border: 1px solid $primary-color; color: $primary-color; background: transparent; padding: 4px 10px; border-radius: $radius-sm; font-size: $font-size-xs; }
|
||||
.btn-action.btn-danger { border-color: $error-color; color: $error-color; }
|
||||
|
||||
.empty { padding: $space-xl; text-align: center; color: $text-tertiary; }
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: $space-md;
|
||||
margin-top: $space-lg;
|
||||
}
|
||||
.btn-page { padding: $space-xs $space-md; border: 1px solid $border-color; border-radius: $radius-sm; background: #fff; }
|
||||
.page-text { color: $text-secondary; font-size: $font-size-sm; }
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
border-radius: $radius-lg;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $space-lg;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: $font-size-base;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
color: $text-tertiary;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: $space-lg;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: $space-lg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $space-sm;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: $font-size-sm;
|
||||
font-weight: 500;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
padding: $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-primary;
|
||||
background: $background-secondary;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
padding: $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-primary;
|
||||
background: $background-secondary;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: $space-md;
|
||||
justify-content: flex-end;
|
||||
padding: $space-lg;
|
||||
border-top: 1px solid $border-color;
|
||||
background: $background-secondary;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-confirm {
|
||||
padding: $space-sm $space-lg;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
font-size: $font-size-sm;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: $background-tertiary;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background: $primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
25
pages/mall/admin/service/config.uvue
Normal file
25
pages/mall/admin/service/config.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('config')
|
||||
const title = ref<string>('config')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
25
pages/mall/admin/service/index.uvue
Normal file
25
pages/mall/admin/service/index.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('service-list')
|
||||
const title = ref<string>('客服列表')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
544
pages/mall/admin/service/message.uvue
Normal file
544
pages/mall/admin/service/message.uvue
Normal file
@@ -0,0 +1,544 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="service-message">
|
||||
<view class="service-container">
|
||||
<view class="content-card">
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">状态</text>
|
||||
<picker
|
||||
:range="statusOptions"
|
||||
range-key="label"
|
||||
:value="filterStatus"
|
||||
@change="(e) => filterStatus = statusOptions[e.detail.value].value"
|
||||
>
|
||||
<view class="picker-wrapper">
|
||||
<text>{{ statusOptions.find(s => s.value === filterStatus)?.label || '全部' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">用户名</text>
|
||||
<input v-model="keyword" class="filter-input" placeholder="请输入用户名" />
|
||||
</view>
|
||||
<button class="btn-search" @click="handleSearch">搜索</button>
|
||||
<button class="btn-reset" @click="handleReset">重置</button>
|
||||
</view>
|
||||
<view v-if="selectedIds.length > 0" class="batch-actions">
|
||||
<text class="batch-text">已选择 {{ selectedIds.length }} 条</text>
|
||||
<button class="btn-batch-reply" @click="handleBatchReply">批量标记已读</button>
|
||||
<button class="btn-batch-delete" @click="handleBatchDelete">批量删除</button>
|
||||
<button class="btn-cancel-select" @click="handleCancelSelect">取消选择</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="table">
|
||||
<view class="table-header">
|
||||
<view class="cell cell-checkbox">
|
||||
<input type="checkbox" :checked="selectAll" @change="handleSelectAll" />
|
||||
</view>
|
||||
<view class="cell cell-id">ID</view>
|
||||
<view class="cell cell-user">用户</view>
|
||||
<view class="cell cell-contact">联系方式</view>
|
||||
<view class="cell cell-content">消息内容</view>
|
||||
<view class="cell cell-status">状态</view>
|
||||
<view class="cell cell-time">时间</view>
|
||||
<view class="cell cell-actions">操作</view>
|
||||
</view>
|
||||
<view v-if="list.length === 0" class="empty">暂无数据</view>
|
||||
<view v-for="item in list" :key="item.id" class="table-row" :class="{ selected: selectedIds.includes(item.id) }">
|
||||
<view class="cell cell-checkbox">
|
||||
<input type="checkbox" :checked="selectedIds.includes(item.id)" @change="handleSelectItem(item.id)" />
|
||||
</view>
|
||||
<view class="cell cell-id">{{ item.id }}</view>
|
||||
<view class="cell cell-user">{{ item.user }}</view>
|
||||
<view class="cell cell-contact">{{ item.contact }}</view>
|
||||
<view class="cell cell-content">{{ item.content }}</view>
|
||||
<view class="cell cell-status">
|
||||
<text class="badge" :class="{ on: item.status === 1 }">{{ item.status === 1 ? '已读' : '未读' }}</text>
|
||||
</view>
|
||||
<view class="cell cell-time">{{ item.created_at }}</view>
|
||||
<view class="cell cell-actions">
|
||||
<button class="btn-action" @click="handleReply(item.id)">回复</button>
|
||||
<button class="btn-action btn-danger" @click="handleDelete(item.id)">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pagination">
|
||||
<button class="btn-page" :disabled="page === 1" @click="prevPage">上一页</button>
|
||||
<text class="page-text">第 {{ page }} 页</text>
|
||||
<button class="btn-page" :disabled="page >= totalPage" @click="nextPage">下一页</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 回复消息 -->
|
||||
<view v-if="showReplyModal" class="modal-overlay" @click.stop="closeReplyModal">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">回复消息</text>
|
||||
<button class="modal-close" @click="closeReplyModal">关闭</button>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-group">
|
||||
<text class="form-label">消息内容</text>
|
||||
<view class="reply-content-box">{{ replyingMessage.content }}</view>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<text class="form-label">回复内容 <text class="required">*</text></text>
|
||||
<textarea v-model="replyForm.reply" class="form-textarea" placeholder="请输入回复内容"></textarea>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button class="btn-cancel" @click="closeReplyModal">取消</button>
|
||||
<button class="btn-confirm" @click="handleSendReply">发送回复</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
import { getMessageList, replyMessage, deleteMessage } from './service.uts'
|
||||
|
||||
const list = ref<any[]>([])
|
||||
const page = ref<number>(1)
|
||||
const pageSize = 10
|
||||
const total = ref<number>(0)
|
||||
const selectedIds = ref<number[]>([])
|
||||
const selectAll = ref<boolean>(false)
|
||||
const showReplyModal = ref<boolean>(false)
|
||||
const keyword = ref<string>('')
|
||||
const filterStatus = ref<any>('')
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '已读', value: 1 },
|
||||
{ label: '未读', value: 0 }
|
||||
]
|
||||
|
||||
const replyingMessage = ref<any>({ id: null, content: '' })
|
||||
const replyForm = ref({ reply: '' })
|
||||
|
||||
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize)))
|
||||
|
||||
const loadList = async () => {
|
||||
const res = await getMessageList({ page: page.value, limit: pageSize, keyword: keyword.value, status: filterStatus.value })
|
||||
list.value = res.items
|
||||
total.value = res.total
|
||||
selectAll.value = false
|
||||
selectedIds.value = []
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
page.value = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
keyword.value = ''
|
||||
filterStatus.value = ''
|
||||
page.value = 1
|
||||
selectedIds.value = []
|
||||
loadList()
|
||||
}
|
||||
|
||||
const handleSelectAll = () => {
|
||||
selectAll.value = !selectAll.value
|
||||
if (selectAll.value) {
|
||||
selectedIds.value = list.value.map(item => item.id)
|
||||
} else {
|
||||
selectedIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectItem = (id: number) => {
|
||||
const index = selectedIds.value.indexOf(id)
|
||||
if (index > -1) {
|
||||
selectedIds.value.splice(index, 1)
|
||||
} else {
|
||||
selectedIds.value.push(id)
|
||||
}
|
||||
selectAll.value = selectedIds.value.length === list.value.length
|
||||
}
|
||||
|
||||
const handleCancelSelect = () => {
|
||||
selectedIds.value = []
|
||||
selectAll.value = false
|
||||
}
|
||||
|
||||
const handleReply = (id: number) => {
|
||||
const item = list.value.find(i => i.id === id)
|
||||
if (item) {
|
||||
replyingMessage.value = item
|
||||
replyForm.value.reply = ''
|
||||
showReplyModal.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const handleSendReply = async () => {
|
||||
if (!replyForm.value.reply.trim()) {
|
||||
uni.showToast({ title: '请输入回复内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
await replyMessage(replyingMessage.value.id, { reply: replyForm.value.reply })
|
||||
uni.showToast({ title: '回复成功', icon: 'success' })
|
||||
closeReplyModal()
|
||||
loadList()
|
||||
}
|
||||
|
||||
const closeReplyModal = () => {
|
||||
showReplyModal.value = false
|
||||
replyingMessage.value = { id: null, content: '' }
|
||||
replyForm.value.reply = ''
|
||||
}
|
||||
|
||||
const handleBatchReply = () => {
|
||||
uni.showToast({ title: '批量标记已读功能开发中', icon: 'none' })
|
||||
}
|
||||
|
||||
const handleBatchDelete = () => {
|
||||
uni.showModal({
|
||||
title: '批量删除',
|
||||
content: `确认删除选中的 ${selectedIds.value.length} 条消息吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
for (const id of selectedIds.value) {
|
||||
await deleteMessage(id)
|
||||
}
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
uni.showModal({
|
||||
title: '删除消息',
|
||||
content: '确认删除该消息吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await deleteMessage(id)
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const prevPage = () => {
|
||||
if (page.value > 1) {
|
||||
page.value--
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (page.value < totalPage.value) {
|
||||
page.value++
|
||||
loadList()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
|
||||
.service-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
padding: $space-lg;
|
||||
background: $background-secondary;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
padding: $space-lg;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
.header-title { font-size: $font-size-lg; font-weight: 600; color: $text-primary; }
|
||||
|
||||
.table { border: 1px solid $border-color; border-radius: $radius-sm; overflow: hidden; }
|
||||
.table-header,
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.table-header { background: $background-tertiary; font-weight: 600; }
|
||||
.table-row { border-top: 1px solid $border-color; }
|
||||
|
||||
.cell { padding: $space-sm $space-md; font-size: $font-size-sm; color: $text-primary; display: flex; align-items: center; }
|
||||
.cell-id { width: 60px; justify-content: center; }
|
||||
.cell-user { width: 100px; }
|
||||
.cell-contact { width: 140px; }
|
||||
.cell-content { flex: 1; }
|
||||
.cell-status { width: 100px; justify-content: center; }
|
||||
.cell-time { width: 180px; }
|
||||
.cell-actions { width: 160px; gap: $space-sm; justify-content: center; }
|
||||
|
||||
.badge { padding: 2px 10px; border-radius: $radius-sm; background: lighten($error-color, 40%); color: $error-color; font-size: $font-size-xs; }
|
||||
.badge.on { background: lighten($success-color, 40%); color: $success-color; }
|
||||
|
||||
.btn-action { border: 1px solid $primary-color; color: $primary-color; background: transparent; padding: 4px 10px; border-radius: $radius-sm; font-size: $font-size-xs; }
|
||||
.btn-action.btn-danger { border-color: $error-color; color: $error-color; }
|
||||
|
||||
.empty { padding: $space-xl; text-align: center; color: $text-tertiary; }
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: $space-md;
|
||||
margin-top: $space-lg;
|
||||
}
|
||||
.btn-page { padding: $space-xs $space-md; border: 1px solid $border-color; border-radius: $radius-sm; background: #fff; }
|
||||
.page-text { color: $text-secondary; font-size: $font-size-sm; }
|
||||
|
||||
.filter-section {
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: $space-md;
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: $space-sm;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
color: $text-secondary;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.picker-wrapper {
|
||||
padding: $space-xs $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
padding: $space-xs $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
background: $primary-color;
|
||||
color: #fff;
|
||||
padding: $space-xs $space-lg;
|
||||
border: none;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: transparent;
|
||||
color: $primary-color;
|
||||
border: 1px solid $primary-color;
|
||||
padding: $space-xs $space-lg;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: $space-md;
|
||||
padding: $space-md;
|
||||
background: #fef3c7;
|
||||
border-radius: $radius-sm;
|
||||
margin-top: $space-md;
|
||||
}
|
||||
|
||||
.batch-text {
|
||||
font-size: $font-size-sm;
|
||||
color: #92400e;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.btn-batch-reply,
|
||||
.btn-batch-delete,
|
||||
.btn-cancel-select {
|
||||
padding: $space-xs $space-md;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
font-size: $font-size-xs;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-batch-reply {
|
||||
background: $primary-color;
|
||||
}
|
||||
|
||||
.btn-batch-delete {
|
||||
background: $error-color;
|
||||
}
|
||||
|
||||
.btn-cancel-select {
|
||||
background: #d1d5db;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.table-row.selected {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.cell-checkbox {
|
||||
width: 50px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
border-radius: $radius-lg;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $space-lg;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: $font-size-base;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
color: $text-tertiary;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: $space-lg;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: $space-lg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $space-sm;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: $font-size-sm;
|
||||
font-weight: 500;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
.reply-content-box {
|
||||
padding: $space-sm;
|
||||
background: $background-secondary;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-primary;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
padding: $space-sm;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-primary;
|
||||
background: $background-secondary;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: $space-md;
|
||||
justify-content: flex-end;
|
||||
padding: $space-lg;
|
||||
border-top: 1px solid $border-color;
|
||||
background: $background-secondary;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-confirm {
|
||||
padding: $space-sm $space-lg;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
font-size: $font-size-sm;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: $background-tertiary;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background: $primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
25
pages/mall/admin/service/script.uvue
Normal file
25
pages/mall/admin/service/script.uvue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('script')
|
||||
const title = ref<string>('script')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
246
pages/mall/admin/service/service.uts
Normal file
246
pages/mall/admin/service/service.uts
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* 客服管理服务层
|
||||
* 可先使用 mock 数据,后续替换为实际 API
|
||||
*/
|
||||
|
||||
export type ServiceItem = {
|
||||
id: number
|
||||
name: string
|
||||
account: string
|
||||
avatar: string
|
||||
status: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type ScriptItem = {
|
||||
id: number
|
||||
title: string
|
||||
content: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type MessageItem = {
|
||||
id: number
|
||||
user: string
|
||||
contact: string
|
||||
content: string
|
||||
status: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type AutoReplyItem = {
|
||||
id: number
|
||||
keyword: string
|
||||
reply: string
|
||||
status: number
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type ServiceConfig = {
|
||||
workTime: string
|
||||
autoReply: number
|
||||
welcomeText: string
|
||||
}
|
||||
|
||||
export const getServiceList = (params: any = {}): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let items: ServiceItem[] = [
|
||||
{ id: 1, name: '张客服', account: 'service01', avatar: '/static/user/avatar-1.png', status: 1, created_at: '2026-01-28 10:30:00' },
|
||||
{ id: 2, name: '李客服', account: 'service02', avatar: '/static/user/avatar-2.png', status: 1, created_at: '2026-01-27 09:15:00' },
|
||||
{ id: 3, name: '王客服', account: 'service03', avatar: '/static/user/avatar-3.png', status: 0, created_at: '2026-01-26 18:20:00' }
|
||||
]
|
||||
|
||||
if (params.keyword) {
|
||||
items = items.filter(item =>
|
||||
item.name.includes(params.keyword) || item.account.includes(params.keyword)
|
||||
)
|
||||
}
|
||||
|
||||
if (params.status !== undefined && params.status !== null) {
|
||||
items = items.filter(item => item.status === params.status)
|
||||
}
|
||||
|
||||
const total = items.length
|
||||
const start = ((params.page || 1) - 1) * (params.limit || 10)
|
||||
const end = start + (params.limit || 10)
|
||||
|
||||
resolve({
|
||||
items: items.slice(start, end),
|
||||
total
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteService = (id: number): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const batchDeleteService = (ids: number[]): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '批量删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const batchUpdateServiceStatus = (ids: number[], status: number): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '状态更新成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const getScriptList = (params: any = {}): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let items: ScriptItem[] = [
|
||||
{ id: 1, title: '欢迎语', content: '您好,欢迎咨询,我们将尽快为您服务。', updated_at: '2026-01-28 10:30:00' },
|
||||
{ id: 2, title: '退款说明', content: '退款将在 1-3 个工作日内原路返回。', updated_at: '2026-01-27 11:12:00' }
|
||||
]
|
||||
|
||||
if (params.title) {
|
||||
items = items.filter(item => item.title.includes(params.title))
|
||||
}
|
||||
|
||||
const total = items.length
|
||||
const start = ((params.page || 1) - 1) * (params.limit || 10)
|
||||
const end = start + (params.limit || 10)
|
||||
|
||||
resolve({
|
||||
items: items.slice(start, end),
|
||||
total
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const saveScript = (data: any): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '保存成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteScript = (id: number): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const getMessageList = (params: any = {}): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let items: MessageItem[] = [
|
||||
{ id: 1, user: '小王', contact: '138****1122', content: '订单什么时候发货?', status: 0, created_at: '2026-01-28 09:10:00' },
|
||||
{ id: 2, user: '小李', contact: '微信: li***', content: '能否开票?', status: 1, created_at: '2026-01-27 16:45:00' }
|
||||
]
|
||||
|
||||
if (params.keyword) {
|
||||
items = items.filter(item =>
|
||||
item.user.includes(params.keyword) || item.content.includes(params.keyword)
|
||||
)
|
||||
}
|
||||
|
||||
if (params.status !== undefined && params.status !== null) {
|
||||
items = items.filter(item => item.status === params.status)
|
||||
}
|
||||
|
||||
const total = items.length
|
||||
const start = ((params.page || 1) - 1) * (params.limit || 10)
|
||||
const end = start + (params.limit || 10)
|
||||
|
||||
resolve({
|
||||
items: items.slice(start, end),
|
||||
total
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const replyMessage = (id: number, data: any): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '回复成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteMessage = (id: number): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const batchReplyMessage = (ids: number[]): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '批量标记成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const batchDeleteMessage = (ids: number[]): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '批量删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const getAutoReplyList = (params: any = {}): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let items: AutoReplyItem[] = [
|
||||
{ id: 1, keyword: '退货', reply: '退货请联系在线客服处理。', status: 1, updated_at: '2026-01-28 08:30:00' },
|
||||
{ id: 2, keyword: '物流', reply: '物流信息可在订单详情查看。', status: 1, updated_at: '2026-01-27 12:20:00' }
|
||||
]
|
||||
|
||||
if (params.keyword) {
|
||||
items = items.filter(item => item.keyword.includes(params.keyword) || item.reply.includes(params.keyword))
|
||||
}
|
||||
|
||||
if (params.status !== undefined && params.status !== null) {
|
||||
items = items.filter(item => item.status === params.status)
|
||||
}
|
||||
|
||||
const total = items.length
|
||||
const start = ((params.page || 1) - 1) * (params.limit || 10)
|
||||
const end = start + (params.limit || 10)
|
||||
|
||||
resolve({
|
||||
items: items.slice(start, end),
|
||||
total
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const saveAutoReply = (data: any): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '保存成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteAutoReply = (id: number): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const batchDeleteAutoReply = (ids: number[]): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '批量删除成功' }), 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const getServiceConfig = (): Promise<ServiceConfig> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
workTime: '09:00-18:00',
|
||||
autoReply: 1,
|
||||
welcomeText: '您好,欢迎咨询,我们将尽快为您服务。'
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
|
||||
export const saveServiceConfig = (data: any): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ success: true, message: '保存成功' }), 300)
|
||||
})
|
||||
}
|
||||
81
pages/mall/admin/setting/system/admin.uvue
Normal file
81
pages/mall/admin/setting/system/admin.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 管理员管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/setting/system/config.uvue
Normal file
81
pages/mall/admin/setting/system/config.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">系统配置</text>
|
||||
<text class="page-subtitle">Component: SettingSystemConfig</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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 系统配置 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/setting/system/role.uvue
Normal file
81
pages/mall/admin/setting/system/role.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 角色管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
81
pages/mall/admin/statistic/index.uvue
Normal file
81
pages/mall/admin/statistic/index.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 数据概览 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="system">
|
||||
<AdminLayout currentPage="sys-basic">
|
||||
<view class="Page">
|
||||
<view class="Header">
|
||||
<text class="Title">设置</text>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="api-collect">
|
||||
<view class="page">
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部<EFBFBD>? -->
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部) -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
@@ -23,17 +23,17 @@
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">商品采集配置</text>
|
||||
<text class="p">TODO:在这里实现 商品采集配置 的页面内容<EFBFBD>?/text>
|
||||
<text class="p">TODO:在这里实现 商品采集配置 的页面内容。</text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)<EFBFBD>?/text>
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)。</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端<EFBFBD>?/text>
|
||||
<text class="li">2)列<EFBFBD>?表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控<EFBFBD>?/text>
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端)。</text>
|
||||
<text class="li">2)列表/表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控制。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -41,6 +41,7 @@
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
@@ -56,7 +57,7 @@ const onPrimaryAction = () => {
|
||||
<style scoped>
|
||||
.page { width: 100%; height: 100%; background-color: #f6f7fb; }
|
||||
|
||||
/* 顶部栏(适配沉浸式状态栏<EFBFBD>?*/
|
||||
/* 顶部栏(适配沉浸式状态栏) */
|
||||
.topbar { position: sticky; top: 0; z-index: 10; background-color: #fff; }
|
||||
.status-bar { height: var(--status-bar-height); }
|
||||
.nav { height: 44px; display: flex; align-items: center; padding: 0 12px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eef0f5; }
|
||||
@@ -82,4 +83,3 @@ const onPrimaryAction = () => {
|
||||
.list { margin-top: 8px; }
|
||||
.li { font-size: 13px; color: #333; line-height: 20px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="api-logistics">
|
||||
<view class="page">
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部<EFBFBD>? -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部) -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
|
||||
<text class="nav-title">物流查询配置</text>
|
||||
<text class="nav-title">物流查询配置</text>
|
||||
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">物流查询配置</text>
|
||||
<text class="p">TODO:在这里实现 物流查询配置 的页面内容<E58685>?/text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)<64>?/text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端<E5908E>?/text>
|
||||
<text class="li">2)列<EFBC89>?表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控<E99990>?/text>
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">物流查询配置</text>
|
||||
<text class="p">TODO:在这里实现 物流查询配置 的页面内容。</text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)。</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端)。</text>
|
||||
<text class="li">2)列表/表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控制。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
@@ -56,7 +57,7 @@ const onPrimaryAction = () => {
|
||||
<style scoped>
|
||||
.page { width: 100%; height: 100%; background-color: #f6f7fb; }
|
||||
|
||||
/* 顶部栏(适配沉浸式状态栏<EFBFBD>?*/
|
||||
/* 顶部栏(适配沉浸式状态栏) */
|
||||
.topbar { position: sticky; top: 0; z-index: 10; background-color: #fff; }
|
||||
.status-bar { height: var(--status-bar-height); }
|
||||
.nav { height: 44px; display: flex; align-items: center; padding: 0 12px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eef0f5; }
|
||||
@@ -82,4 +83,3 @@ const onPrimaryAction = () => {
|
||||
.list { margin-top: 8px; }
|
||||
.li { font-size: 13px; color: #333; line-height: 20px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="api-pay">
|
||||
<view class="page">
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部<EFBFBD>? -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部) -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
|
||||
<text class="nav-title">商城支付配置</text>
|
||||
<text class="nav-title">商城支付配置</text>
|
||||
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">商城支付配置</text>
|
||||
<text class="p">TODO:在这里实现 商城支付配置 的页面内容<E58685>?/text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)<64>?/text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端<E5908E>?/text>
|
||||
<text class="li">2)列<EFBC89>?表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控<E99990>?/text>
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">商城支付配置</text>
|
||||
<text class="p">TODO:在这里实现 商城支付配置 的页面内容。</text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)。</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端)。</text>
|
||||
<text class="li">2)列表/表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控制。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
@@ -56,7 +57,7 @@ const onPrimaryAction = () => {
|
||||
<style scoped>
|
||||
.page { width: 100%; height: 100%; background-color: #f6f7fb; }
|
||||
|
||||
/* 顶部栏(适配沉浸式状态栏<EFBFBD>?*/
|
||||
/* 顶部栏(适配沉浸式状态栏) */
|
||||
.topbar { position: sticky; top: 0; z-index: 10; background-color: #fff; }
|
||||
.status-bar { height: var(--status-bar-height); }
|
||||
.nav { height: 44px; display: flex; align-items: center; padding: 0 12px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eef0f5; }
|
||||
@@ -82,4 +83,3 @@ const onPrimaryAction = () => {
|
||||
.list { margin-top: 8px; }
|
||||
.li { font-size: 13px; color: #333; line-height: 20px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="api-sms">
|
||||
<view class="page">
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部<EFBFBD>? -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部) -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
|
||||
<text class="nav-title">短信接口配置</text>
|
||||
<text class="nav-title">短信接口配置</text>
|
||||
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">短信接口配置</text>
|
||||
<text class="p">TODO:在这里实现 短信接口配置 的页面内容<E58685>?/text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)<64>?/text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端<E5908E>?/text>
|
||||
<text class="li">2)列<EFBC89>?表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控<E99990>?/text>
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">短信接口配置</text>
|
||||
<text class="p">TODO:在这里实现 短信接口配置 的页面内容。</text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)。</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端)。</text>
|
||||
<text class="li">2)列表/表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控制。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
@@ -56,7 +57,7 @@ const onPrimaryAction = () => {
|
||||
<style scoped>
|
||||
.page { width: 100%; height: 100%; background-color: #f6f7fb; }
|
||||
|
||||
/* 顶部栏(适配沉浸式状态栏<EFBFBD>?*/
|
||||
/* 顶部栏(适配沉浸式状态栏) */
|
||||
.topbar { position: sticky; top: 0; z-index: 10; background-color: #fff; }
|
||||
.status-bar { height: var(--status-bar-height); }
|
||||
.nav { height: 44px; display: flex; align-items: center; padding: 0 12px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eef0f5; }
|
||||
@@ -82,4 +83,3 @@ const onPrimaryAction = () => {
|
||||
.list { margin-top: 8px; }
|
||||
.li { font-size: 13px; color: #333; line-height: 20px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="api-waybill">
|
||||
<view class="page">
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部<EFBFBD>? -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部) -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
|
||||
<text class="nav-title">电子面单配置</text>
|
||||
<text class="nav-title">电子面单配置</text>
|
||||
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">电子面单配置</text>
|
||||
<text class="p">TODO:在这里实现 电子面单配置 的页面内容<E58685>?/text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)<64>?/text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端<E5908E>?/text>
|
||||
<text class="li">2)列<EFBC89>?表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控<E99990>?/text>
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">电子面单配置</text>
|
||||
<text class="p">TODO:在这里实现 电子面单配置 的页面内容。</text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)。</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端)。</text>
|
||||
<text class="li">2)列表/表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控制。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
@@ -56,7 +57,7 @@ const onPrimaryAction = () => {
|
||||
<style scoped>
|
||||
.page { width: 100%; height: 100%; background-color: #f6f7fb; }
|
||||
|
||||
/* 顶部栏(适配沉浸式状态栏<EFBFBD>?*/
|
||||
/* 顶部栏(适配沉浸式状态栏) */
|
||||
.topbar { position: sticky; top: 0; z-index: 10; background-color: #fff; }
|
||||
.status-bar { height: var(--status-bar-height); }
|
||||
.nav { height: 44px; display: flex; align-items: center; padding: 0 12px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eef0f5; }
|
||||
@@ -82,4 +83,3 @@ const onPrimaryAction = () => {
|
||||
.list { margin-top: 8px; }
|
||||
.li { font-size: 13px; color: #333; line-height: 20px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="api-yht-config">
|
||||
<view class="page">
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部<EFBFBD>? -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
<!-- TopBar (navigationStyle: custom 时需要自己做顶部) -->
|
||||
<view class="topbar">
|
||||
<view class="status-bar" />
|
||||
<view class="nav">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="nav-icon">‹</text>
|
||||
<text class="nav-text">返回</text>
|
||||
</view>
|
||||
|
||||
<text class="nav-title">一号通配<EFBFBD>?/text>
|
||||
<text class="nav-title">一号通配置</text>
|
||||
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">一号通配<E9809A>?/text>
|
||||
<text class="p">TODO:在这里实现 一号通配<E9809A>?的页面内容<E58685>?/text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)<64>?/text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端<E5908E>?/text>
|
||||
<text class="li">2)列<EFBC89>?表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控<E99990>?/text>
|
||||
<view class="nav-right">
|
||||
<text class="nav-action" @click="onPrimaryAction">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
|
||||
<!-- Main -->
|
||||
<scroll-view class="body" scroll-y="true">
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h2">一号通配置</text>
|
||||
<text class="p">TODO:在这里实现 一号通配置 的页面内容。</text>
|
||||
<view class="divider" />
|
||||
<text class="p muted">提示:当前为可跑的占位模板(TopBar + Container + Card)。</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="h3">建议你下一步先补齐</text>
|
||||
<view class="list">
|
||||
<text class="li">1)接口:拉取/保存数据(API 协议对齐后端)。</text>
|
||||
<text class="li">2)列表/表单:搜索、分页、增删改</text>
|
||||
<text class="li">3)权限:按钮/路由级权限控制。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
@@ -57,7 +57,7 @@ const onPrimaryAction = () => {
|
||||
<style scoped>
|
||||
.page { width: 100%; height: 100%; background-color: #f6f7fb; }
|
||||
|
||||
/* 顶部栏(适配沉浸式状态栏<EFBFBD>?*/
|
||||
/* 顶部栏(适配沉浸式状态栏) */
|
||||
.topbar { position: sticky; top: 0; z-index: 10; background-color: #fff; }
|
||||
.status-bar { height: var(--status-bar-height); }
|
||||
.nav { height: 44px; display: flex; align-items: center; padding: 0 12px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eef0f5; }
|
||||
@@ -83,4 +83,3 @@ const onPrimaryAction = () => {
|
||||
.list { margin-top: 8px; }
|
||||
.li { font-size: 13px; color: #333; line-height: 20px; }
|
||||
</style>
|
||||
|
||||
|
||||
27
pages/mall/admin/system/index.uvue
Normal file
27
pages/mall/admin/system/index.uvue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面占位 (自动生成)</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('sys-basic')
|
||||
const title = ref<string>('index')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
</style>
|
||||
|
||||
|
||||
81
pages/mall/admin/user/grade/card.uvue
Normal file
81
pages/mall/admin/user/grade/card.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 卡密会员 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user