Merge remote-tracking branch 'origin/huangzhenbao-admin'
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
<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>
|
||||
|
||||
|
||||
264
pages/mall/admin/marketing/points/config/index.uvue
Normal file
264
pages/mall/admin/marketing/points/config/index.uvue
Normal file
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<view class="admin-marketing-integral-config">
|
||||
<view class="config-card box-shadow">
|
||||
<view class="tabs-container">
|
||||
<view class="tab-item" :class="{ active: activeTab == 0 }" @click="activeTab = 0">基础配置</view>
|
||||
<view class="tab-item" :class="{ active: activeTab == 1 }" @click="activeTab = 1">抵扣配置</view>
|
||||
<view class="tab-item" :class="{ active: activeTab == 2 }" @click="activeTab = 2">过期配置</view>
|
||||
</view>
|
||||
|
||||
<view class="config-body">
|
||||
<!-- 基础配置 -->
|
||||
<view v-if="activeTab == 0" class="config-section">
|
||||
<view class="form-item">
|
||||
<view class="item-label">积分名称:</view>
|
||||
<view class="item-content">
|
||||
<input class="admin-input" v-model="form.integral_name" placeholder="积分" />
|
||||
<text class="desc">用户看到的积分名称,如:金豆、积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="item-label">积分赠送单位:</view>
|
||||
<view class="item-content">
|
||||
<input class="admin-input-long" type="number" v-model="form.integral_unit" />
|
||||
<text class="desc">下单消费1元赠送多少积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="item-label">积分冻结时间:</view>
|
||||
<view class="item-content">
|
||||
<view class="input-with-unit">
|
||||
<input class="admin-input-unit" type="number" v-model="form.freeze_time" />
|
||||
<text class="unit">天</text>
|
||||
</view>
|
||||
<text class="desc">订单确认收货后,赠送积分需要冻结多少天转为可用积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 抵扣配置 -->
|
||||
<view v-if="activeTab == 1" class="config-section">
|
||||
<view class="form-item">
|
||||
<view class="item-label">积分抵扣比例:</view>
|
||||
<view class="item-content">
|
||||
<view class="input-with-unit">
|
||||
<input class="admin-input-unit" v-model="form.integral_ratio" />
|
||||
<text class="unit">元</text>
|
||||
</view>
|
||||
<text class="desc">1积分可抵扣多少元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="item-label">积分抵扣上限:</view>
|
||||
<view class="item-content">
|
||||
<view class="input-with-unit">
|
||||
<input class="admin-input-unit" type="number" v-model="form.integral_max" />
|
||||
<text class="unit">%</text>
|
||||
</view>
|
||||
<text class="desc">单笔订单最高可抵扣金额比例(0-100)</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 过期配置 -->
|
||||
<view v-if="activeTab == 2" class="config-section">
|
||||
<view class="form-item">
|
||||
<view class="item-label">积分有效期:</view>
|
||||
<view class="item-content">
|
||||
<radio-group class="radio-group" @change="validTypeChange">
|
||||
<label class="radio-label">
|
||||
<radio value="0" :checked="form.valid_type == 0" /> 永久有效
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<radio value="1" :checked="form.valid_type == 1" /> 固定时长
|
||||
</label>
|
||||
</radio-group>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item" v-if="form.valid_type == 1">
|
||||
<view class="item-label">固定时长:</view>
|
||||
<view class="item-content">
|
||||
<view class="input-with-unit">
|
||||
<input class="admin-input-unit" type="number" v-model="form.valid_year" />
|
||||
<text class="unit">年</text>
|
||||
</view>
|
||||
<text class="desc">从积分获得日期起,多少年后过期</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="form-ops">
|
||||
<button class="submit-btn" @click="handleSubmit">保存设置</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const activeTab = ref(0)
|
||||
const form = reactive({
|
||||
integral_name: '积分',
|
||||
integral_unit: 10,
|
||||
integral_ratio: 0.1,
|
||||
integral_max: 50,
|
||||
freeze_time: 7,
|
||||
valid_type: 0,
|
||||
valid_year: 1
|
||||
})
|
||||
|
||||
const validTypeChange = (e: any) => {
|
||||
form.valid_type = parseInt(e.detail.value as string)
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.box-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.config-card {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
min-height: 650px;
|
||||
}
|
||||
|
||||
/* 顶部标签 */
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.tab-item {
|
||||
padding: 15px 25px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.tab-item.active {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #2d8cf0;
|
||||
}
|
||||
|
||||
.config-body {
|
||||
padding: 40px 60px;
|
||||
min-height: 550px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 25px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
width: 140px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.admin-input, .admin-input-long {
|
||||
width: 320px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.input-with-unit {
|
||||
width: 320px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.admin-input-unit {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
}
|
||||
.unit {
|
||||
padding: 0 12px;
|
||||
background-color: #f8f8f9;
|
||||
border-left: 1px solid #dcdee2;
|
||||
font-size: 12px;
|
||||
color: #808695;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #c5c8ce;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
.radio-label {
|
||||
margin-right: 20px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-ops {
|
||||
margin-top: 50px;
|
||||
padding-left: 140px;
|
||||
}
|
||||
.submit-btn {
|
||||
width: 120px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
background-color: #2d8cf0;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<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>
|
||||
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="Page">
|
||||
<view class="Header">
|
||||
<text class="Title">积分统计</text>
|
||||
<text class="SubTitle">marketing/points/index</text>
|
||||
</view>
|
||||
|
||||
<view class="Card">
|
||||
<text class="Label">页面参数(query)</text>
|
||||
<text class="Mono">{{ params }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const params = ref('')
|
||||
const tab = ref('stats')
|
||||
|
||||
onLoad((options) => {
|
||||
params.value = JSON.stringify(options ?? {})
|
||||
tab.value = options?.tab || 'stats'
|
||||
})
|
||||
|
||||
const currentPage = computed(() => {
|
||||
switch (tab.value) {
|
||||
case 'goods': return 'points-goods'
|
||||
case 'order': return 'points-order'
|
||||
case 'record': return 'points-record'
|
||||
case 'config': return 'points-config'
|
||||
case 'lottery-list': return 'lottery-list'
|
||||
case 'lottery-config': return 'lottery-config'
|
||||
case 'groupbuy-goods': return 'groupbuy-goods'
|
||||
case 'groupbuy-list': return 'groupbuy-list'
|
||||
case 'seckill-goods': return 'seckill-goods'
|
||||
case 'seckill-list': return 'seckill-list'
|
||||
case 'seckill-config': return 'seckill-config'
|
||||
case 'member-type': return 'member-type'
|
||||
case 'member-rights': return 'member-rights'
|
||||
case 'member-card': return 'member-card'
|
||||
case 'member-record': return 'member-record'
|
||||
case 'member-config': return 'member-config'
|
||||
case 'live-room': return 'live-room'
|
||||
case 'live-goods': return 'live-goods'
|
||||
case 'live-anchor': return 'live-anchor'
|
||||
case 'recharge-amount': return 'recharge-amount'
|
||||
case 'recharge-config': return 'recharge-config'
|
||||
case 'recharge-record': return 'recharge-record'
|
||||
case 'newcomer': return 'newcomer'
|
||||
default: return 'points-stats'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.Page {
|
||||
padding: 24rpx;
|
||||
}
|
||||
.Header {
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
.Title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.SubTitle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.Card {
|
||||
margin-top: 24rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
.Label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.Mono {
|
||||
font-size: 24rpx;
|
||||
font-family: monospace;
|
||||
line-height: 36rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +0,0 @@
|
||||
<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>
|
||||
447
pages/mall/admin/marketing/points/orders/index.uvue
Normal file
447
pages/mall/admin/marketing/points/orders/index.uvue
Normal file
@@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<view class="admin-marketing-integral-order">
|
||||
<!-- 顶部状态选项卡 -->
|
||||
<view class="status-tabs box-shadow">
|
||||
<view v-for="(item, index) in statusOptions" :key="index"
|
||||
:class="['tab-item', currentStatus == item.value ? 'active' : '']"
|
||||
@click="currentStatus = item.value">
|
||||
<text class="tab-label">{{ item.label }}</text>
|
||||
<text class="tab-count">({{ item.count }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<view class="filter-card box-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label">订单状态:</text>
|
||||
<picker :range="statusOptions" range-key="label" @change="statusChange">
|
||||
<view class="picker-value">{{ getStatusLabel(currentStatus) }} <text class="iconfont icon-arrow-down"></text></view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label">创建时间:</text>
|
||||
<view class="date-range-mock">
|
||||
<text class="date-text">全部</text>
|
||||
<text class="iconfont icon-calendar"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item search-box">
|
||||
<input class="admin-input" placeholder="订单号/收货人/电话" v-model="searchQuery" />
|
||||
<button class="admin-btn admin-btn-primary search-btn" @click="handleSearch">搜索</button>
|
||||
<button class="admin-btn admin-btn-default reset-btn" @click="handleReset">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<view class="table-toolbar">
|
||||
<button class="admin-btn admin-btn-default">订单导出</button>
|
||||
</view>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<view class="table-card box-shadow">
|
||||
<view class="table-header">
|
||||
<text class="col-200">订单号</text>
|
||||
<text class="col-150">用户信息</text>
|
||||
<text class="col-250">商品信息</text>
|
||||
<text class="col-100">兑换积分</text>
|
||||
<text class="col-100">订单状态</text>
|
||||
<text class="col-180">下单时间</text>
|
||||
<text class="col-120 center">操作</text>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="(item, index) in tableData" :key="index" class="table-row">
|
||||
<view class="col-200">
|
||||
<text class="order-id-text">{{ item.orderId }}</text>
|
||||
</view>
|
||||
|
||||
<view class="col-150">
|
||||
<text class="user-nickname-uid">{{ item.nickname }}/{{ item.uid }}</text>
|
||||
</view>
|
||||
|
||||
<view class="col-250 product-cell">
|
||||
<view class="tabBox">
|
||||
<image class="tabBox_img" :src="item.image" mode="aspectFill"></image>
|
||||
<view class="tabBox_right">
|
||||
<text class="tabBox_tit">{{ item.storeName }} | {{ item.sku }}</text>
|
||||
<text class="tabBox_pice">积分{{ item.integral }} x {{ item.num }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="col-100">
|
||||
<text class="integral-val">{{ item.integral }}</text>
|
||||
</view>
|
||||
|
||||
<view class="col-100">
|
||||
<text class="status-text">{{ getStatusText(item.status) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="col-180">
|
||||
<text class="time-text">{{ item.addTime }}</text>
|
||||
</view>
|
||||
|
||||
<view class="col-120 center ops-cell">
|
||||
<text class="op-link" v-if="item.status === 0" @click="handleShip(item)">发送货</text>
|
||||
<text class="op-link" @click="handleMore(item)">更多 <text class="iconfont icon-arrow-down" style="font-size: 10px;"></text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页区域 -->
|
||||
<CommonPagination
|
||||
v-if="total > 0"
|
||||
:total="total"
|
||||
:loading="false"
|
||||
:currentPage="currentPage"
|
||||
:pageSize="currentSize"
|
||||
:pageSizeOptionLabels="pageSizeOptionLabels"
|
||||
:pageSizeIndex="pageSizeIndex"
|
||||
:visiblePages="visiblePages"
|
||||
:totalPage="totalPage"
|
||||
:jumpPageInput="jumpPageInput"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@page-change="handlePageChange"
|
||||
@update:jumpPageInput="updateJumpPageInput"
|
||||
@jump-page="handleJumpPage"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script uts>
|
||||
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
|
||||
export default {
|
||||
components: {
|
||||
CommonPagination
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentStatus: -1,
|
||||
searchQuery: '',
|
||||
total: 8,
|
||||
currentPage: 1,
|
||||
currentSize: 15,
|
||||
jumpPageInput: '',
|
||||
statusOptions: [
|
||||
{ label: '全部', value: -1, count: 8 },
|
||||
{ label: '待发货', value: 0, count: 6 },
|
||||
{ label: '待收货', value: 1, count: 1 },
|
||||
{ label: '待评价', value: 2, count: 0 },
|
||||
{ label: '已完成', value: 3, count: 1 }
|
||||
] as any[],
|
||||
tableData: [
|
||||
{
|
||||
orderId: 'wx719261472118538240',
|
||||
nickname: '故事的小黄花',
|
||||
uid: 80674,
|
||||
storeName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤UWG440060',
|
||||
sku: 'L,卡其',
|
||||
image: '/static/logo.png',
|
||||
integral: 200,
|
||||
num: 1,
|
||||
status: 0,
|
||||
addTime: '2025-11-10 18:48:18'
|
||||
},
|
||||
{
|
||||
orderId: 'wx717772249846775808',
|
||||
nickname: '蓝桥春雪',
|
||||
uid: 80582,
|
||||
storeName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤UWG440060',
|
||||
sku: 'M,卡其',
|
||||
image: '/static/logo.png',
|
||||
integral: 200,
|
||||
num: 1,
|
||||
status: 0,
|
||||
addTime: '2025-11-06 16:10:39'
|
||||
},
|
||||
{
|
||||
orderId: 'wx717763196697444352',
|
||||
nickname: './',
|
||||
uid: 78504,
|
||||
storeName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤UWG440060',
|
||||
sku: 'S,卡其',
|
||||
image: '/static/logo.png',
|
||||
integral: 200,
|
||||
num: 1,
|
||||
status: 0,
|
||||
addTime: '2025-11-06 15:34:41'
|
||||
},
|
||||
{
|
||||
orderId: 'wx714911802768490496',
|
||||
nickname: '177****1167',
|
||||
uid: 36391,
|
||||
storeName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤UWG440060',
|
||||
sku: 'XL,卡其',
|
||||
image: '/static/logo.png',
|
||||
integral: 0,
|
||||
num: 1,
|
||||
status: 0,
|
||||
addTime: '2025-10-29 18:44:16'
|
||||
},
|
||||
{
|
||||
orderId: 'wx665016819706232832',
|
||||
nickname: '陈修然',
|
||||
uid: 75658,
|
||||
storeName: 'MLB官方 男女情侣软顶棒球帽明星同款运动帽遮阳鸭舌帽休闲CP19',
|
||||
sku: 'XS,纽约洋基队/绿色',
|
||||
image: '/static/logo.png',
|
||||
integral: 299,
|
||||
num: 1,
|
||||
status: 0,
|
||||
addTime: '2025-06-14 02:19:25'
|
||||
}
|
||||
] as any[]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pageSizeOptions(): number[] { return [10, 15, 20, 30, 50] },
|
||||
pageSizeOptionLabels(): string[] { return (this.pageSizeOptions as number[]).map((n: number) => `${n}条/页`) },
|
||||
pageSizeIndex(): number {
|
||||
const idx = (this.pageSizeOptions as number[]).indexOf(this.currentSize as number)
|
||||
return idx >= 0 ? idx : 0
|
||||
},
|
||||
totalPage(): number { return Math.max(1, Math.ceil((this.total as number) / (this.currentSize as number))) },
|
||||
visiblePages(): number[] {
|
||||
const t = this.totalPage as number
|
||||
const cur = this.currentPage as number
|
||||
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
|
||||
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
|
||||
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
|
||||
return [1, -1, cur - 1, cur, cur + 1, -1, t]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getStatusLabel(val : number) : string {
|
||||
const item = this.statusOptions.find((opt: any) => opt.value === val)
|
||||
return item ? item.label : '全部'
|
||||
},
|
||||
getStatusText(status : number) : string {
|
||||
switch(status) {
|
||||
case 0: return '未发货';
|
||||
case 1: return '待收货';
|
||||
case 2: return '待评价';
|
||||
case 3: return '已完成';
|
||||
default: return '未知';
|
||||
}
|
||||
},
|
||||
handleShip(item: any) {
|
||||
uni.showToast({ title: '去发货: ' + item.orderId, icon: 'none' })
|
||||
},
|
||||
handleMore(item: any) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['订单详情', '订单记录', '订单备注'],
|
||||
success: (res) => {
|
||||
uni.showToast({ title: '点击了' + res.tapIndex, icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
statusChange(e : any) {
|
||||
this.currentStatus = this.statusOptions[e.detail.value].value as number
|
||||
},
|
||||
handleSearch() {
|
||||
uni.showToast({ title: '查询', icon: 'none' })
|
||||
},
|
||||
handleReset() {
|
||||
this.searchQuery = ''
|
||||
this.currentStatus = -1
|
||||
},
|
||||
handlePageChange(p: number) { this.currentPage = p as any },
|
||||
handlePageSizeChange(e: any) {
|
||||
const idx = Number(e.detail.value)
|
||||
this.currentSize = ((this.pageSizeOptions as number[])[idx] ?? (this.pageSizeOptions as number[])[0]) as any
|
||||
this.currentPage = 1 as any
|
||||
},
|
||||
handleJumpPage() {
|
||||
const p = parseInt(this.jumpPageInput as string)
|
||||
if (!isNaN(p) && p >= 1 && p <= (this.totalPage as number)) this.currentPage = p as any
|
||||
},
|
||||
updateJumpPageInput(val: string) { this.jumpPageInput = val as any }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-marketing-integral-order {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 顶部状态卡片 */
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.tab-item {
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab-item.active {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #2d8cf0;
|
||||
}
|
||||
.tab-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
.tab-count {
|
||||
font-size: 12px;
|
||||
margin-left: 4px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 筛选卡片 */
|
||||
.filter-card {
|
||||
padding: 20px;
|
||||
}
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 24px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.date-range-mock {
|
||||
width: 280px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
}
|
||||
.date-text { font-size: 13px; color: #c0c4cc; }
|
||||
|
||||
.admin-input {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.search-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
padding: 12px 10px;
|
||||
}
|
||||
.table-header text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #515a6e;
|
||||
}
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
/* 列宽定义 */
|
||||
.col-100 { width: 100px; }
|
||||
.col-120 { width: 120px; }
|
||||
.col-150 { width: 150px; }
|
||||
.col-180 { width: 180px; }
|
||||
.col-200 { width: 200px; }
|
||||
.col-250 { width: 250px; flex: 1; }
|
||||
.center { text-align: center; justify-content: center; }
|
||||
|
||||
.order-id-text { font-size: 13px; color: #515a6e; }
|
||||
.user-nickname-uid { font-size: 13px; color: #515a6e; }
|
||||
|
||||
/* 商品信息列 */
|
||||
.product-cell {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tabBox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.tabBox_img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.tabBox_right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tabBox_tit {
|
||||
font-size: 12px;
|
||||
color: #515a6e;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.tabBox_pice {
|
||||
font-size: 12px;
|
||||
color: #808695;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.integral-val { font-size: 13px; color: #515a6e; }
|
||||
.status-text { font-size: 13px; color: #515a6e; }
|
||||
.time-text { font-size: 13px; color: #515a6e; }
|
||||
|
||||
.ops-cell {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.op-link {
|
||||
font-size: 13px;
|
||||
color: #2d8cf0;
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 分页区域已迁至 CommonPagination 组件 */
|
||||
</style>
|
||||
|
||||
400
pages/mall/admin/marketing/points/products/index.uvue
Normal file
400
pages/mall/admin/marketing/points/products/index.uvue
Normal file
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<view class="admin-marketing-integral-product">
|
||||
<view class="content-body">
|
||||
<!-- 搜索过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">创建时间:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="calendar-ic">📅</text>
|
||||
<text class="date-placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">上架状态:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">商品搜索:</text>
|
||||
<input class="search-input" placeholder="请输入商品名称, ID" v-model="searchQuery" />
|
||||
</view>
|
||||
<view class="btn-query" @click="handleSearch">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="card-header">
|
||||
<view class="btn-primary-blue" @click="handleAdd">
|
||||
<text class="btn-txt">+ 添加积分商品</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表格 -->
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th th-id">ID</view>
|
||||
<view class="th th-img">商品图片</view>
|
||||
<view class="th th-title">活动标题</view>
|
||||
<view class="th th-integral">兑换积分</view>
|
||||
<view class="th th-limit">限量</view>
|
||||
<view class="th th-remain">限量剩余</view>
|
||||
<view class="th th-time">创建时间</view>
|
||||
<view class="th th-sort">排序</view>
|
||||
<view class="th th-status">状态</view>
|
||||
<view class="th th-ops">操作</view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="(item, index) in productList" :key="item.id" class="table-row">
|
||||
<view class="td td-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td td-img">
|
||||
<image class="product-thumb" :src="item.image" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="td td-title">
|
||||
<text class="title-txt line-clamp-2">{{ item.title }}</text>
|
||||
</view>
|
||||
<view class="td td-integral"><text class="td-txt">{{ item.integral }}</text></view>
|
||||
<view class="td td-limit"><text class="td-txt">{{ item.limit }}</text></view>
|
||||
<view class="td td-remain"><text class="td-txt">{{ item.remain }}</text></view>
|
||||
<view class="td td-time"><text class="td-txt-small">{{ item.createTime }}</text></view>
|
||||
<view class="td td-sort"><text class="td-txt">{{ item.sort }}</text></view>
|
||||
<view class="td td-status">
|
||||
<view :class="['switch-box', item.status ? 'active' : '']" @click="toggleStatus(index)">
|
||||
<view class="switch-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td td-ops">
|
||||
<view class="op-links">
|
||||
<text class="op-link">兑换记录</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link" @click="handleEdit(item)">编辑</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link">复制</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link text-danger">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<CommonPagination
|
||||
v-if="total > 0"
|
||||
:total="total"
|
||||
:loading="false"
|
||||
:currentPage="currentPage"
|
||||
:pageSize="pageSize"
|
||||
:pageSizeOptionLabels="pageSizeOptionLabels"
|
||||
:pageSizeIndex="pageSizeIndex"
|
||||
:visiblePages="visiblePages"
|
||||
:totalPage="totalPage"
|
||||
:jumpPageInput="jumpPageInput"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@page-change="handlePageChange"
|
||||
@update:jumpPageInput="(val : string) => { jumpPageInput = val }"
|
||||
@jump-page="handleJumpPage"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
|
||||
|
||||
interface ProductItem {
|
||||
id: number
|
||||
image: string
|
||||
title: string
|
||||
integral: number
|
||||
limit: number
|
||||
remain: number
|
||||
createTime: string
|
||||
sort: number
|
||||
status: boolean
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const total = ref(3)
|
||||
|
||||
const productList = ref<ProductItem[]>([
|
||||
{
|
||||
id: 48,
|
||||
image: 'https://img14.360buyimg.com/n1/jfs/t1/172605/32/17036/114175/609a473eE6997455c/df82c6168e36712b.jpg',
|
||||
title: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤UWG440060',
|
||||
integral: 0,
|
||||
limit: 4,
|
||||
remain: 0,
|
||||
createTime: '2025-10-24 14:29:19',
|
||||
sort: 9999,
|
||||
status: true
|
||||
},
|
||||
{
|
||||
id: 43,
|
||||
image: 'https://img12.360buyimg.com/n1/jfs/t1/185449/19/11995/4379/60d96d27E6a877c8e/3c38d4e92a2a7a5a.jpg',
|
||||
title: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇水蓝/传...',
|
||||
integral: 100,
|
||||
limit: 1,
|
||||
remain: 0,
|
||||
createTime: '2025-05-13 15:37:46',
|
||||
sort: 9998,
|
||||
status: true
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
image: 'https://img13.360buyimg.com/n1/jfs/t1/192173/5/11913/21447/60e57e95Ef82688f3/bc875f643e8c95a3.jpg',
|
||||
title: '劳伦斯意式极简大平层设计师款直排真皮沙发简约客厅别墅大小户型',
|
||||
integral: 6860,
|
||||
limit: 1,
|
||||
remain: 0,
|
||||
createTime: '2025-05-13 15:38:02',
|
||||
sort: 9996,
|
||||
status: true
|
||||
}
|
||||
])
|
||||
|
||||
const handleSearch = () => { console.log('Searching...') }
|
||||
const handleAdd = () => { console.log('Adding...') }
|
||||
const handleEdit = (item: ProductItem) => { console.log('Editing...', item.id) }
|
||||
const toggleStatus = (index: number) => {
|
||||
productList.value[index].status = !productList.value[index].status
|
||||
}
|
||||
|
||||
// 分页适配状态
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(15)
|
||||
let jumpPageInput = ''
|
||||
const pageSizeOptions = [10, 15, 20, 30, 50]
|
||||
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
|
||||
const pageSizeIndex = computed(() => {
|
||||
const idx = pageSizeOptions.indexOf(pageSize.value)
|
||||
return idx >= 0 ? idx : 0
|
||||
})
|
||||
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
|
||||
const visiblePages = computed(() => {
|
||||
const t = totalPage.value
|
||||
const cur = currentPage.value
|
||||
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
|
||||
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
|
||||
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
|
||||
return [1, -1, cur - 1, cur, cur + 1, -1, t]
|
||||
})
|
||||
const handlePageChange = (p: number) => { currentPage.value = p }
|
||||
const handlePageSizeChange = (e: any) => {
|
||||
const idx = Number(e.detail.value)
|
||||
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
|
||||
currentPage.value = 1
|
||||
}
|
||||
const handleJumpPage = () => {
|
||||
const p = parseInt(jumpPageInput)
|
||||
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-marketing-integral-product {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label-txt { font-size: 14px; color: #606266; white-space: nowrap; }
|
||||
|
||||
.date-picker-mock {
|
||||
width: 280px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.calendar-ic { font-size: 14px; color: #c0c4cc; }
|
||||
.date-placeholder { font-size: 13px; color: #c0c4cc; }
|
||||
|
||||
.select-mock {
|
||||
width: 180px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.select-val { font-size: 14px; color: #c0c4cc; }
|
||||
.arrow-down { font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
.search-input {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #2d8cf0;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格卡片 */
|
||||
.table-card {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.card-header { padding: 20px; }
|
||||
|
||||
.btn-primary-blue {
|
||||
background-color: #2d8cf0;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
}
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.table-container { padding: 0 20px 20px; }
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 10px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 12px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td-txt { font-size: 14px; color: #515a6e; }
|
||||
.td-txt-small { font-size: 13px; color: #515a6e; }
|
||||
|
||||
/* 列表各列宽度控制 */
|
||||
.th-id, .td-id { width: 60px; }
|
||||
.th-img, .td-img { width: 80px; }
|
||||
.th-title, .td-title { flex: 1; min-width: 200px; }
|
||||
.th-integral, .td-integral { width: 100px; }
|
||||
.th-limit, .td-limit { width: 80px; }
|
||||
.th-remain, .td-remain { width: 100px; }
|
||||
.th-time, .td-time { width: 160px; }
|
||||
.th-sort, .td-sort { width: 80px; }
|
||||
.th-status, .td-status { width: 80px; }
|
||||
.th-ops, .td-ops { width: 220px; justify-content: flex-end; }
|
||||
|
||||
.product-thumb {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.title-txt { font-size: 13px; color: #333; line-height: 1.5; }
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Switch 开关 */
|
||||
.switch-box {
|
||||
width: 44px;
|
||||
height: 22px;
|
||||
background-color: #dcdfe6;
|
||||
border-radius: 11px;
|
||||
position: relative;
|
||||
transition: background-color 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.switch-box.active { background-color: #2d8cf0; }
|
||||
.switch-dot {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #fff;
|
||||
border-radius: 9px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.switch-box.active .switch-dot { transform: translateX(22px); }
|
||||
|
||||
.op-links { display: flex; flex-direction: row; align-items: center; }
|
||||
.op-link { color: #2d8cf0; font-size: 13px; cursor: pointer; margin: 0 5px; }
|
||||
.op-split { color: #e8eaec; margin: 0 5px; }
|
||||
.text-danger { color: #ed4014; }
|
||||
|
||||
/* 分页区域已迁至 CommonPagination 组件 */
|
||||
|
||||
</style>
|
||||
|
||||
314
pages/mall/admin/marketing/points/record/index.uvue
Normal file
314
pages/mall/admin/marketing/points/record/index.uvue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<view class="admin-marketing-integral-record">
|
||||
<!-- 筛选 -->
|
||||
<view class="box-shadow filter-card">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label">时间选择:</text>
|
||||
<view class="date-range-mock">
|
||||
<text class="date-text">全部</text>
|
||||
<text class="iconfont icon-calendar" style="font-size: 16px; color: #c0c4cc;"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label">交易类型:</text>
|
||||
<picker mode="selector" :range="typeOptions" range-key="label" @change="typeChange">
|
||||
<view class="admin-input-picker">
|
||||
<text>{{ typeLabel }}</text>
|
||||
<text class="iconfont icon-arrow-down" style="font-size: 14px; color: #c0c4cc;"></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label">搜索:</text>
|
||||
<input class="admin-input" v-model="searchQuery" placeholder="请输入昵称" />
|
||||
<button class="search-btn btn-primary" @click="handleSearch">搜索</button>
|
||||
<button class="reset-btn btn-default" @click="handleReset">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表格 -->
|
||||
<view class="box-shadow table-card">
|
||||
<view class="table-header">
|
||||
<view class="col-80"><text>ID</text></view>
|
||||
<view class="col-150"><text>关联订单</text></view>
|
||||
<view class="col-150"><text>交易时间</text></view>
|
||||
<view class="col-100"><text>交易积分</text></view>
|
||||
<view class="col-120"><text>用户</text></view>
|
||||
<view class="col-120"><text>交易类型</text></view>
|
||||
<view class="col-150"><text>备注</text></view>
|
||||
<view class="col-100"><text>备注</text></view>
|
||||
</view>
|
||||
<view class="table-content">
|
||||
<view v-for="(item, index) in tableData" :key="index" class="table-row">
|
||||
<view class="col-80"><text class="cell-text">{{ item.id }}</text></view>
|
||||
<view class="col-150"><text class="cell-link">{{ item.relation || '-' }}</text></view>
|
||||
<view class="col-150"><text class="cell-text">{{ item.add_time }}</text></view>
|
||||
<view class="col-100">
|
||||
<text :class="item.pm == 1 ? 'z-price' : 'f-price'">{{ item.pm == 1 ? '+' : '-' }} {{ item.number }}</text>
|
||||
</view>
|
||||
<view class="col-120"><text class="cell-text">{{ item.nickname }}</text></view>
|
||||
<view class="col-120"><text class="cell-text">{{ item.type_name }}</text></view>
|
||||
<view class="col-150"><text class="cell-text mark-text">{{ item.mark }}</text></view>
|
||||
<view class="col-100">
|
||||
<text class="op-link" @click="handleMark(item)">修改备注</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页区域 -->
|
||||
<CommonPagination
|
||||
v-if="total > 0"
|
||||
:total="total"
|
||||
:loading="false"
|
||||
:currentPage="currentPage"
|
||||
:pageSize="currentSize"
|
||||
:pageSizeOptionLabels="pageSizeOptionLabels"
|
||||
:pageSizeIndex="pageSizeIndex"
|
||||
:visiblePages="visiblePages"
|
||||
:totalPage="totalPage"
|
||||
:jumpPageInput="jumpPageInput"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@page-change="handlePageChange"
|
||||
@update:jumpPageInput="updateJumpPageInput"
|
||||
@jump-page="handleJumpPage"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script uts>
|
||||
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
|
||||
export default {
|
||||
components: {
|
||||
CommonPagination
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchQuery: '',
|
||||
currentType: '',
|
||||
typeOptions: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '积分订单', value: 'integral_order' },
|
||||
{ label: '签到奖励', value: 'sign' },
|
||||
{ label: '后台充值', value: 'system' }
|
||||
] as any[],
|
||||
total: 5,
|
||||
currentPage: 1,
|
||||
currentSize: 15,
|
||||
jumpPageInput: '',
|
||||
tableData: [
|
||||
{
|
||||
id: 1256,
|
||||
relation: '',
|
||||
add_time: '2025-11-12 09:30',
|
||||
number: 10,
|
||||
pm: 1,
|
||||
nickname: '故事的小黄花',
|
||||
type_name: '签到奖励',
|
||||
mark: '连续签到3天奖励'
|
||||
},
|
||||
{
|
||||
id: 1255,
|
||||
relation: 'wx719261472118538240',
|
||||
add_time: '2025-11-10 18:48',
|
||||
number: 200,
|
||||
pm: 0,
|
||||
nickname: '故事的小黄花',
|
||||
type_name: '积分兑换',
|
||||
mark: '兑换商品扣除'
|
||||
},
|
||||
{
|
||||
id: 1254,
|
||||
relation: '',
|
||||
add_time: '2025-11-05 14:20',
|
||||
number: 1000,
|
||||
pm: 1,
|
||||
nickname: '蓝桥春雪',
|
||||
type_name: '后台充值',
|
||||
mark: '管理员手动充值'
|
||||
}
|
||||
] as any[]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
typeLabel(): string {
|
||||
const found = this.typeOptions.find((o: any): boolean => o.value == this.currentType)
|
||||
return found != null ? (found['label'] as string) : '全部'
|
||||
},
|
||||
pageSizeOptions(): number[] { return [10, 15, 20, 30, 50] },
|
||||
pageSizeOptionLabels(): string[] { return (this.pageSizeOptions as number[]).map((n: number) => `${n}条/页`) },
|
||||
pageSizeIndex(): number {
|
||||
const idx = (this.pageSizeOptions as number[]).indexOf(this.currentSize as number)
|
||||
return idx >= 0 ? idx : 0
|
||||
},
|
||||
totalPage(): number { return Math.max(1, Math.ceil((this.total as number) / (this.currentSize as number))) },
|
||||
visiblePages(): number[] {
|
||||
const t = this.totalPage as number
|
||||
const cur = this.currentPage as number
|
||||
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
|
||||
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
|
||||
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
|
||||
return [1, -1, cur - 1, cur, cur + 1, -1, t]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
typeChange(e: any) {
|
||||
this.currentType = this.typeOptions[e.detail.value].value as string
|
||||
},
|
||||
handleSearch() {
|
||||
uni.showToast({ title: '搜索: ' + this.searchQuery, icon: 'none' })
|
||||
},
|
||||
handleReset() {
|
||||
this.searchQuery = ''
|
||||
this.currentType = ''
|
||||
},
|
||||
handleMark(item: any) {
|
||||
uni.showToast({ title: '修改备注: ' + item.id, icon: 'none' })
|
||||
},
|
||||
handlePageChange(p: number) { this.currentPage = p as any },
|
||||
handlePageSizeChange(e: any) {
|
||||
const idx = Number(e.detail.value)
|
||||
this.currentSize = ((this.pageSizeOptions as number[])[idx] ?? (this.pageSizeOptions as number[])[0]) as any
|
||||
this.currentPage = 1 as any
|
||||
},
|
||||
handleJumpPage() {
|
||||
const p = parseInt(this.jumpPageInput as string)
|
||||
if (!isNaN(p) && p >= 1 && p <= (this.totalPage as number)) this.currentPage = p as any
|
||||
},
|
||||
updateJumpPageInput(val: string) { this.jumpPageInput = val as any }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-marketing-integral-record {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding: 20px;
|
||||
}
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 24px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-range-mock {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
}
|
||||
.date-text { font-size: 13px; color: #c0c4cc; }
|
||||
|
||||
.admin-input-picker {
|
||||
width: 150px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.admin-input-picker text { font-size: 14px; color: #606266; }
|
||||
|
||||
.admin-input {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-btn, .reset-btn {
|
||||
margin-left: 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.btn-primary { background-color: #2d8cf0; color: #fff; border: none; }
|
||||
.btn-default { background-color: #fff; color: #606266; border: 1px solid #dcdfe6; }
|
||||
|
||||
/* 表格 */
|
||||
.table-card {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.table-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
padding: 12px 10px;
|
||||
}
|
||||
.table-header text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #515a6e;
|
||||
}
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.col-80 { width: 80px; }
|
||||
.col-100 { width: 100px; }
|
||||
.col-120 { width: 120px; }
|
||||
.col-150 { width: 150px; }
|
||||
.col-180 { width: 180px; }
|
||||
|
||||
.cell-text { font-size: 13px; color: #515a6e; }
|
||||
.cell-link { font-size: 13px; color: #2d8cf0; }
|
||||
.mark-text { color: #808695; }
|
||||
|
||||
.z-price { font-size: 14px; color: #19be6b; font-weight: bold; }
|
||||
.f-price { font-size: 14px; color: #ed4014; font-weight: bold; }
|
||||
|
||||
.op-link {
|
||||
font-size: 13px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 分页区域已迁至 CommonPagination 组件 */
|
||||
</style>
|
||||
|
||||
|
||||
409
pages/mall/admin/marketing/points/statistics/index.uvue
Normal file
409
pages/mall/admin/marketing/points/statistics/index.uvue
Normal file
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<view class="admin-marketing-integral-statistic">
|
||||
<view class="content-body">
|
||||
<!-- 顶部时间选择 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">时间选择:</text>
|
||||
<AnalyticsDateRangePicker
|
||||
:initialStartDate="startDate"
|
||||
:initialEndDate="endDate"
|
||||
@apply="onApplyRange"
|
||||
@clear="onClearRange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标卡片 -->
|
||||
<view class="stats-row">
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="sc-left bg-blue">
|
||||
<text class="sc-icon">💠</text>
|
||||
</view>
|
||||
<view class="sc-right">
|
||||
<text class="sc-val">{{ statsTotal.current.toFixed(2) }}</text>
|
||||
<text class="sc-label">当前积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="sc-left bg-orange">
|
||||
<text class="sc-icon">🪙</text>
|
||||
</view>
|
||||
<view class="sc-right">
|
||||
<text class="sc-val">{{ statsTotal.income.toFixed(2) }}</text>
|
||||
<text class="sc-label">累计总积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="sc-left bg-green">
|
||||
<text class="sc-icon">💎</text>
|
||||
</view>
|
||||
<view class="sc-right">
|
||||
<text class="sc-val">{{ statsTotal.expend.toFixed(2) }}</text>
|
||||
<text class="sc-label">累计消耗积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 积分使用趋势 -->
|
||||
<view class="chart-card border-shadow">
|
||||
<view class="chart-header">
|
||||
<text class="chart-title">积分使用趋势</text>
|
||||
<view class="chart-legend">
|
||||
<text class="down-ic">📥</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-body">
|
||||
<AnalyticsMultiLineChart :xLabels="dates" :series="trendSeries" :height="350" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部两个分析卡片 -->
|
||||
<view class="bottom-analysis">
|
||||
<!-- 积分来源分析 -->
|
||||
<view class="analysis-card border-shadow">
|
||||
<view class="analysis-header">
|
||||
<text class="ah-title">积分来源分析</text>
|
||||
<view class="btn-toggle" @click="toggleSourceStyle">
|
||||
<text class="toggle-txt">切换样式</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-content">
|
||||
<!-- 饼图样式 -->
|
||||
<view v-if="sourceStyle === 'pie'" class="pie-layout-new anim-fade">
|
||||
<AnalyticsPieChart :items="sourceData" :height="300" />
|
||||
</view>
|
||||
|
||||
<!-- 列表样式 -->
|
||||
<view v-else class="list-layout anim-fade">
|
||||
<view class="list-head">
|
||||
<text class="lh-col" style="width: 50px;">来源</text>
|
||||
<text class="lh-col" style="flex: 1; text-align: center;">金额</text>
|
||||
<text class="lh-col" style="width: 200px; text-align: right;">占比率</text>
|
||||
</view>
|
||||
<view class="list-body">
|
||||
<view v-for="(item, index) in sourceData" :key="item.label" class="list-row">
|
||||
<view class="lr-rank"><text class="rank-txt">{{ index + 1 }}</text></view>
|
||||
<text class="lr-label">{{ item.label }}</text>
|
||||
<text class="lr-val">{{ item.value }}</text>
|
||||
<view class="lr-progress-box">
|
||||
<view class="prog-bg">
|
||||
<view class="prog-inner" :style="{width: item.percent + '%'}"></view>
|
||||
</view>
|
||||
<text class="prog-txt">{{ item.percent }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 积分消耗分析 -->
|
||||
<view class="analysis-card border-shadow">
|
||||
<view class="analysis-header">
|
||||
<text class="ah-title">积分消耗</text>
|
||||
<view class="btn-toggle" @click="toggleConsumeStyle">
|
||||
<text class="toggle-txt">切换样式</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-content">
|
||||
<!-- 饼图样式 -->
|
||||
<view v-if="consumeStyle === 'pie'" class="pie-layout-new anim-fade">
|
||||
<AnalyticsPieChart :items="consumeData" :height="300" />
|
||||
</view>
|
||||
|
||||
<!-- 列表样式 -->
|
||||
<view v-else class="list-layout anim-fade">
|
||||
<view class="list-head">
|
||||
<text class="lh-col" style="width: 50px;">来源</text>
|
||||
<text class="lh-col" style="flex: 1; text-align: center;">金额</text>
|
||||
<text class="lh-col" style="width: 200px; text-align: right;">占比率</text>
|
||||
</view>
|
||||
<view class="list-body">
|
||||
<view v-for="(item, index) in consumeData" :key="item.label" class="list-row">
|
||||
<view class="lr-rank"><text class="rank-txt">{{ index + 1 }}</text></view>
|
||||
<text class="lr-label">{{ item.label }}</text>
|
||||
<text class="lr-val">{{ item.value }}</text>
|
||||
<view class="lr-progress-box">
|
||||
<view class="prog-bg">
|
||||
<view class="prog-inner" :style="{width: item.percent + '%'}"></view>
|
||||
</view>
|
||||
<text class="prog-txt">{{ item.percent }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import AnalyticsPieChart from '@/components/analytics/AnalyticsPieChart.uvue'
|
||||
import AnalyticsMultiLineChart from '@/components/analytics/AnalyticsMultiLineChart.uvue'
|
||||
import AnalyticsDateRangePicker from '@/components/analytics/AnalyticsDateRangePicker.uvue'
|
||||
import { fetchIntegralStats, IntegralStats } from '@/services/admin/marketingService.uts'
|
||||
|
||||
const startDate = ref('')
|
||||
const endDate = ref('')
|
||||
const isLoading = ref(false)
|
||||
|
||||
const statsTotal = reactive({
|
||||
current: 0,
|
||||
income: 0,
|
||||
expend: 0
|
||||
})
|
||||
|
||||
const dates = ref<string[]>([])
|
||||
const trendSeries = ref<any[]>([])
|
||||
|
||||
const sourceStyle = ref('pie')
|
||||
const consumeStyle = ref('pie')
|
||||
|
||||
const sourceData = ref<any[]>([])
|
||||
const consumeData = ref<any[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
// 默认最近 30 天
|
||||
const end = new Date()
|
||||
const start = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||
startDate.value = start.toISOString().substring(0, 10)
|
||||
endDate.value = end.toISOString().substring(0, 10)
|
||||
|
||||
loadData()
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const st = startDate.value ? (startDate.value + ' 00:00:00') : null
|
||||
const et = endDate.value ? (endDate.value + ' 23:59:59') : null
|
||||
|
||||
const res = await fetchIntegralStats(st!, et!)
|
||||
if (res != null) {
|
||||
// 1. 核心指标
|
||||
statsTotal.current = res.totals.current
|
||||
statsTotal.income = res.totals.income
|
||||
statsTotal.expend = res.totals.expend
|
||||
|
||||
// 2. 趋势图
|
||||
dates.value = res.trend.map(t => t.date_group)
|
||||
trendSeries.value = [
|
||||
{
|
||||
name: '积分积累',
|
||||
data: res.trend.map(t => t.income),
|
||||
color: '#409eff'
|
||||
},
|
||||
{
|
||||
name: '积分消耗',
|
||||
data: res.trend.map(t => t.expend),
|
||||
color: '#19be6b'
|
||||
}
|
||||
]
|
||||
|
||||
// 3. 来源与消耗分布
|
||||
sourceData.value = res.sources.map(s => ({
|
||||
label: s.label,
|
||||
value: s.value,
|
||||
percent: s.percent,
|
||||
color: '#409eff' // 这里可以根据类型映射不同颜色
|
||||
}))
|
||||
|
||||
consumeData.value = res.consumes.map(c => ({
|
||||
label: c.label,
|
||||
value: c.value,
|
||||
percent: c.percent,
|
||||
color: '#19be6b'
|
||||
}))
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载统计失败', icon: 'none' })
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onApplyRange(payload : any) {
|
||||
startDate.value = payload?.start ?? ''
|
||||
endDate.value = payload?.end ?? ''
|
||||
loadData()
|
||||
}
|
||||
|
||||
function onClearRange() {
|
||||
startDate.value = ''
|
||||
endDate.value = ''
|
||||
loadData()
|
||||
}
|
||||
|
||||
const toggleSourceStyle = () => {
|
||||
sourceStyle.value = sourceStyle.value === 'pie' ? 'list' : 'pie'
|
||||
}
|
||||
|
||||
const toggleConsumeStyle = () => {
|
||||
consumeStyle.value = consumeStyle.value === 'pie' ? 'list' : 'pie'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-marketing-integral-statistic {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 时间选择 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
}
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; gap: 12px; }
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
.date-picker-mock {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 5px 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.calendar-ic { font-size: 16px; color: #999; }
|
||||
.date-range { font-size: 14px; color: #333; }
|
||||
|
||||
/* 核心卡片 */
|
||||
.stats-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
.sc-left {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.sc-icon { font-size: 28px; color: #fff; }
|
||||
.bg-blue { background-color: #409eff; }
|
||||
.bg-orange { background-color: #ff9900; }
|
||||
.bg-green { background-color: #19be6b; }
|
||||
|
||||
.sc-right { display: flex; flex-direction: column; }
|
||||
.sc-val { font-size: 28px; font-weight: bold; color: #333; margin-bottom: 5px; }
|
||||
.sc-label { font-size: 14px; color: #999; }
|
||||
|
||||
/* 趋势图 */
|
||||
.chart-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.chart-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.chart-legend { display: flex; flex-direction: row; align-items: center; gap: 20px; }
|
||||
.down-ic { font-size: 18px; color: #999; cursor: pointer; }
|
||||
|
||||
.chart-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 底部两个分析 */
|
||||
.bottom-analysis {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
.analysis-card {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
}
|
||||
.analysis-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.ah-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.btn-toggle {
|
||||
border: 1px solid #dcdfe6;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-txt { font-size: 12px; color: #666; }
|
||||
|
||||
.analysis-content {
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
/* 饼图样式布局 */
|
||||
.pie-layout-new {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 列表样式布局 */
|
||||
.list-layout { display: flex; flex-direction: column; }
|
||||
.list-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.lh-col { font-size: 14px; font-weight: bold; color: #515a6e; }
|
||||
|
||||
.list-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 15px 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.lr-rank { width: 30px; height: 30px; display: flex; align-items: center; }
|
||||
.rank-txt { font-size: 14px; color: #999; }
|
||||
.lr-label { width: 100px; font-size: 14px; color: #333; }
|
||||
.lr-val { flex: 1; font-size: 14px; color: #333; text-align: center; }
|
||||
.lr-progress-box { width: 200px; display: flex; flex-direction: row; align-items: center; gap: 10px; justify-content: flex-end; }
|
||||
.prog-bg { flex: 1; height: 10px; background-color: #f5f5f5; border-radius: 5px; overflow: hidden; }
|
||||
.prog-inner { height: 100%; background-color: #2d8cf0; border-radius: 5px; }
|
||||
.prog-txt { font-size: 13px; color: #666; width: 40px; text-align: right; }
|
||||
|
||||
.anim-fade { animation: fadeIn 0.3s ease-in-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
|
||||
</style>
|
||||
@@ -1,27 +0,0 @@
|
||||
<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user