大致完成页面

This commit is contained in:
2026-02-05 09:01:16 +08:00
parent c411c23b9c
commit d51e6a8f72
40 changed files with 11023 additions and 737 deletions

View File

@@ -0,0 +1,198 @@
<template>
<view class="marketing-checkin-config">
<view class="config-card border-shadow">
<view class="config-header">
<text class="config-title">用户签到配置</text>
</view>
<view class="config-body">
<view class="config-item">
<view class="item-label">
<text class="label-txt">签到开关:</text>
<text class="label-desc">签到开关,商城是否开启签到功能,关闭后隐藏签到入口</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.is_open = true">
<view class="radio-circle" :class="{ checked: config.is_open }"></view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item ml-20" @click="config.is_open = false">
<view class="radio-circle" :class="{ checked: !config.is_open }"></view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">签到模式:</text>
<text class="label-desc">无限制累积和连续签到不会清零周循环每周一会清理累积和连续的记录为0重新开始计算月循环每月一号会清理累积和连续的记录为0重新开始计算</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.mode = 'none'">
<view class="radio-circle" :class="{ checked: config.mode === 'none' }"></view>
<text class="radio-txt">无限制</text>
</view>
<view class="radio-item ml-20" @click="config.mode = 'week'">
<view class="radio-circle" :class="{ checked: config.mode === 'week' }"></view>
<text class="radio-txt">周循环</text>
</view>
<view class="radio-item ml-20" @click="config.mode = 'month'">
<view class="radio-circle" :class="{ checked: config.mode === 'month' }"></view>
<text class="radio-txt">月循环</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">签到提醒:</text>
<text class="label-desc">是否开启签到提醒,提醒方式为短信以及站内信</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.notice_enabled = true">
<view class="radio-circle" :class="{ checked: config.notice_enabled }"></view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item ml-20" @click="config.notice_enabled = false">
<view class="radio-circle" :class="{ checked: !config.notice_enabled }"></view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">签到赠送积分:</text>
<text class="label-desc">签到赠送积分,每日签到赠送的积分值</text>
</view>
<view class="item-content">
<input class="config-input" type="number" v-model="config.integral" />
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">签到赠送经验:</text>
<text class="label-desc">签到赠送用户经验值</text>
</view>
<view class="item-content">
<input class="config-input" type="number" v-model="config.exp" />
</view>
</view>
<view class="config-footer">
<button class="btn-submit" @click="handleSave">提交</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { reactive } from 'vue'
const config = reactive({
is_open: true,
mode: 'none',
notice_enabled: false,
integral: 10,
exp: 1
})
const handleSave = () => {
uni.showToast({ title: '保存成功', icon: 'success' })
}
</script>
<style scoped lang="scss">
.marketing-checkin-config {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.config-card { padding: 24px; }
.config-header {
border-bottom: 1px solid #e8eaec;
padding-bottom: 16px;
margin-bottom: 24px;
}
.config-title {
font-size: 16px;
font-weight: bold;
color: #17233d;
position: relative;
padding-left: 12px;
}
.config-title::before {
content: '';
position: absolute;
left: 0;
top: 4px;
bottom: 4px;
width: 3px;
background: #1890ff;
}
.config-item {
display: flex;
flex-direction: row;
margin-bottom: 30px;
align-items: flex-start;
}
.item-label { width: 220px; display: flex; flex-direction: column; }
.label-txt { font-size: 14px; color: #333; margin-bottom: 4px; }
.label-desc { font-size: 12px; color: #999; line-height: 1.5; padding-right: 20px; }
.item-content { flex: 1; }
.radio-group { display: flex; flex-direction: row; padding-top: 4px; flex-wrap: wrap; }
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; margin-bottom: 10px; }
.radio-circle { width: 14px; height: 14px; border: 1px solid #dcdfe6; border-radius: 50%; margin-right: 6px; position: relative; }
.radio-circle.checked { border-color: #1890ff; }
.radio-circle.checked::after { content: ''; position: absolute; width: 8px; height: 8px; background: #1890ff; border-radius: 50%; top: 2px; left: 2px; }
.radio-txt { font-size: 14px; color: #606266; }
.ml-20 { margin-left: 20px; }
.config-input {
width: 400px;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.config-footer {
margin-top: 40px;
padding-left: 220px;
}
.btn-submit {
width: 80px;
height: 36px;
line-height: 36px;
background: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,271 @@
<template>
<view class="marketing-checkin-reward">
<view class="reward-card border-shadow">
<!-- Tabs -->
<view class="reward-tabs">
<view class="tab-item" :class="{ active: currentTab === 'continuous' }" @click="currentTab = 'continuous'">
<text class="tab-txt">连续签到奖励</text>
</view>
<view class="tab-item" :class="{ active: currentTab === 'cumulative' }" @click="currentTab = 'cumulative'">
<text class="tab-txt">累积签到奖励</text>
</view>
</view>
<view class="action-row">
<button v-if="currentTab === 'continuous'" class="btn-primary" @click="openModal('continuous')">添加连续签到奖励</button>
<button v-else class="btn-primary" @click="openModal('cumulative')">添加累积签到奖励</button>
</view>
<!-- Table -->
<view class="table-container">
<view class="table-head">
<view class="th cell-id">编号</view>
<view class="th cell-type">类型</view>
<view class="th cell-days">签到天数</view>
<view class="th cell-reward">奖励内容</view>
<view class="th cell-status">是否可用</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in displayList" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-type"><text class="td-txt">{{ item.type === 'continuous' ? '连续签到' : '累积签到' }}</text></view>
<view class="td cell-days"><text class="td-txt">{{ item.days }}天</text></view>
<view class="td cell-reward">
<text class="td-txt">积分+{{ item.integral }}, 经验+{{ item.exp }}</text>
</view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_open }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
</view>
</view>
<view class="td cell-op">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-link del ml-10" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<!-- 奖励设置弹窗 -->
<view v-if="showModal" class="modal-mask">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">{{ modalType === 'continuous' ? '连续签到奖励' : '累积签到奖励' }}</text>
<text class="modal-close" @click="showModal = false">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">{{ modalType === 'continuous' ? '连续签到天数' : '累积签到天数' }}</text>
<input class="form-input" v-model="formData.days" type="number" placeholder="0" />
</view>
<view class="form-item">
<text class="form-label">赠送积分:</text>
<input class="form-input" v-model="formData.integral" type="number" placeholder="0" />
</view>
<view class="form-item">
<text class="form-label">赠送经验:</text>
<input class="form-input" v-model="formData.exp" type="number" placeholder="0" />
</view>
</view>
<view class="modal-footer">
<button class="btn-cancel" @click="showModal = false">取消</button>
<button class="btn-submit" @click="handleSubmit">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed } from 'vue'
const currentTab = ref('continuous')
const showModal = ref(false)
const modalType = ref('continuous')
const formData = reactive({
days: '',
integral: '',
exp: ''
})
const continuousList = ref([
{ id: 1, type: 'continuous', days: 3, integral: 20, exp: 2, is_open: true },
{ id: 2, type: 'continuous', days: 7, integral: 50, exp: 5, is_open: true }
])
const cumulativeList = ref([
{ id: 3, type: 'cumulative', days: 15, integral: 100, exp: 10, is_open: true },
{ id: 4, type: 'cumulative', days: 30, integral: 200, exp: 20, is_open: true }
])
const displayList = computed(() => {
return currentTab.value === 'continuous' ? continuousList.value : cumulativeList.value
})
const openModal = (type: string) => {
modalType.value = type
formData.days = ''
formData.integral = ''
formData.exp = ''
showModal.value = true
}
const toggleStatus = (item: any) => {
item.is_open = !item.is_open
uni.showToast({ title: '修改成功', icon: 'success' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '编辑功能暂未对接', icon: 'none' })
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确认删除该奖励配置吗?',
success: (res) => {
if (res.confirm) {
if (currentTab.value === 'continuous') {
continuousList.value = continuousList.value.filter(i => i.id !== item.id)
} else {
cumulativeList.value = cumulativeList.value.filter(i => i.id !== item.id)
}
uni.showToast({ title: '已删除', icon: 'success' })
}
}
})
}
const handleSubmit = () => {
if (!formData.days) {
uni.showToast({ title: '请输入天数', icon: 'none' })
return
}
const newItem = {
id: Date.now(),
type: modalType.value,
days: parseInt(formData.days.toString()),
integral: parseInt(formData.integral.toString() || '0'),
exp: parseInt(formData.exp.toString() || '0'),
is_open: true
}
if (modalType.value === 'continuous') {
continuousList.value.push(newItem)
} else {
cumulativeList.value.push(newItem)
}
showModal.value = false
uni.showToast({ title: '添加成功', icon: 'success' })
}
</script>
<style scoped lang="scss">
.marketing-checkin-reward {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.reward-card { padding: 24px; }
.reward-tabs {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
margin-bottom: 24px;
}
.tab-item {
padding: 12px 24px;
cursor: pointer;
position: relative;
}
.tab-txt { font-size: 14px; color: #515a6e; }
.tab-item.active .tab-txt { color: #1890ff; font-weight: bold; }
.tab-item.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: #1890ff;
}
.action-row { margin-bottom: 24px; }
.btn-primary {
background: #1890ff;
color: #fff;
border: none;
height: 32px;
line-height: 32px;
padding: 0 16px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
}
.table-head { display: flex; flex-direction: row; background: #f8f8f9; border-bottom: 1px solid #e8eaec; }
.th { padding: 12px 8px; font-size: 13px; color: #515a6e; font-weight: bold; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; align-items: center; }
.td { padding: 16px 8px; }
.td-txt { font-size: 13px; color: #515a6e; }
.cell-id { width: 80px; }
.cell-type { width: 120px; }
.cell-days { width: 120px; }
.cell-reward { flex: 1; }
.cell-status { width: 100px; text-align: center; }
.cell-op { width: 150px; text-align: right; }
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-link.del { color: #ff4d4f; }
.ml-10 { margin-left: 10px; }
.switch-mock {
width: 44px; height: 22px; background-color: #bfbfbf; border-radius: 11px;
display: flex; align-items: center; padding: 0 4px; position: relative;
transition: background-color 0.3s; cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 14px; height: 14px; background-color: #fff; border-radius: 50%;
position: absolute; left: 4px; transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 26px; }
/* Modal */
.modal-mask {
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 { width: 500px; background: #fff; border-radius: 4px; }
.modal-header { padding: 16px 24px; border-bottom: 1px solid #e8eaec; display: flex; justify-content: space-between; align-items: center; }
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { font-size: 24px; color: #999; cursor: pointer; }
.modal-body { padding: 24px; }
.modal-footer { padding: 12px 24px; border-top: 1px solid #e8eaec; display: flex; justify-content: flex-end; }
.form-item { display: flex; flex-direction: row; margin-bottom: 20px; align-items: center; }
.form-label { width: 120px; font-size: 14px; color: #606266; }
.form-input { flex: 1; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; }
.btn-cancel { margin-right: 8px; height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; border: 1px solid #dcdfe6; background: #fff; }
.btn-submit { height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; background: #1890ff; color: #fff; border: none; }
</style>

View File

@@ -1,27 +1,364 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-live-anchor">
<view class="action-bar">
<button class="btn-add" @click="showModal = true">添加主播</button>
</view>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-name">名称</view>
<view class="th cell-phone">电话</view>
<view class="th cell-wechat">微信号</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in anchorList" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-name"><text class="td-txt">{{ item.name }}</text></view>
<view class="td cell-phone"><text class="td-txt">{{ item.phone }}</text></view>
<view class="td cell-wechat"><text class="td-txt">{{ item.wechat }}</text></view>
<view class="td cell-op">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">修改</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<view class="pagination-footer">
<view class="page-total"><text class="total-txt">共 {{ anchorList.length }} 条</text></view>
<view class="page-select">
<view class="select-mock mini">
<text class="select-val">15条/页</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="page-btns">
<text class="p-btn disabled"></text>
<text class="p-btn active">1</text>
<text class="p-btn disabled"></text>
</view>
</view>
</view>
</AdminLayout>
<!-- Modal Overlay -->
<view v-if="showModal" class="modal-mask" @click="showModal = false"></view>
<!-- Modal Panel -->
<view v-if="showModal" class="modal-panel">
<view class="modal-header">
<text class="modal-title">添加主播</text>
<text class="modal-close" @click="showModal = false">×</text>
</view>
<view class="modal-content">
<view class="form-item">
<text class="form-label required">主播名称:</text>
<input class="form-input" placeholder="请输入主播名称" />
</view>
<view class="form-item">
<text class="form-label required">主播微信号:</text>
<input class="form-input" placeholder="请输入主播微信号" />
</view>
<view class="form-item">
<text class="form-label required">主播手机号:</text>
<input class="form-input" v-model="formData.phone" placeholder="请输入主播手机号" />
</view>
<view class="form-item">
<text class="form-label">主播图像:</text>
<view class="upload-mock" @click="handleUpload">
<image v-if="formData.avatar" :src="formData.avatar" mode="aspectFill" class="avatar-preview" />
<text v-else class="upload-ic">🖼️</text>
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn-cancel" @click="showModal = false">取消</button>
<button class="btn-confirm" @click="handleSubmit">确定</button>
</view>
</view>
</view>
</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')
const showModal = ref(false)
const formData = ref({
id: 0,
name: '',
wechat: '',
phone: '',
avatar: ''
})
const anchorList = ref([
{
id: 11,
name: '万万',
phone: '15012341234',
wechat: 'xiao112032014'
},
{
id: 10,
name: '打羽毛球',
phone: '13333333333',
wechat: 'evoxwht'
}
])
const handleEdit = (item: any) => {
formData.value = { ...item, avatar: '' }
showModal.value = true
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确定要删除该主播吗?',
success: (res) => {
if (res.confirm) {
anchorList.value = anchorList.value.filter(i => i.id !== item.id)
uni.showToast({ title: '删除成功' })
}
}
})
}
const handleUpload = () => {
uni.chooseImage({
count: 1,
success: (res) => {
formData.value.avatar = res.tempFilePaths[0]
}
})
}
const handleSubmit = () => {
uni.showToast({ title: '操作成功', icon: 'success' })
showModal.value = false
}
</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; }
.marketing-live-anchor {
min-height: 100vh;
background: #f0f2f5;
padding: 16px;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
/* 操作栏 */
.action-bar {
margin-bottom: 16px;
}
.btn-add {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 表格区域 */
.table-card {
padding: 24px;
}
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
/* 各列宽度 */
.cell-id { width: 80px; }
.cell-name { flex: 1; min-width: 150px; }
.cell-phone { width: 180px; }
.cell-wechat { width: 180px; }
.cell-op { width: 120px; text-align: right; }
.op-links {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-split { color: #e8eaec; margin: 0 8px; }
/* Pagination */
.pagination-footer {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 13px; color: #606266; }
.select-mock.mini {
width: 100px;
height: 28px;
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: 12px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
/* Modal Styles */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.45);
z-index: 1000;
}
.modal-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 520px;
background-color: #fff;
z-index: 1001;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
}
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 16px;
font-weight: 600;
color: #262626;
}
.modal-close {
font-size: 24px;
color: #bfbfbf;
cursor: pointer;
}
.modal-content {
padding: 24px;
}
.form-item {
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 14px;
color: #262626;
margin-bottom: 8px;
}
.required::before {
content: '*';
color: #ff4d4f;
margin-right: 4px;
}
.form-input {
width: 100%;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.upload-mock {
width: 80px;
height: 80px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
}
.upload-ic { font-size: 24px; color: #bfbfbf; }
.modal-footer {
padding: 10px 16px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: 8px;
}
.btn-cancel, .btn-confirm {
width: auto;
padding: 0 15px;
height: 32px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
}
.btn-cancel {
background-color: #fff;
border: 1px solid #d9d9d9;
color: #595959;
}
.btn-confirm {
background-color: #1890ff;
border: 1px solid #1890ff;
color: #fff;
}
</style>

View File

@@ -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>('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>

View File

@@ -0,0 +1,488 @@
<template>
<view class="marketing-live-product">
<!-- List View -->
<template v-if="!isAdding">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">审核状态:</text>
<view class="select-mock">
<text class="select-val">全部</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item ml-24">
<text class="label">搜索:</text>
<input class="input-mock" placeholder="请输入商品名称/ID" />
</view>
<button class="btn-query ml-16">查询</button>
</view>
</view>
<view class="action-bar">
<button class="btn-add" @click="isAdding = true">添加商品</button>
</view>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">商品ID</view>
<view class="th cell-info">商品名称</view>
<view class="th cell-price">直播价</view>
<view class="th cell-price">原价</view>
<view class="th cell-stock">库存</view>
<view class="th cell-audit">审核状态</view>
<view class="th cell-status">是否显示</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in productList" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-info">
<view class="info-wrap">
<image class="thumb" :src="item.image" mode="aspectFill"></image>
<text class="p-title line-clamp-2">{{ item.title }}</text>
</view>
</view>
<view class="td cell-price"><text class="td-txt">{{ item.live_price.toFixed(2) }}</text></view>
<view class="td cell-price"><text class="td-txt">{{ item.price.toFixed(2) }}</text></view>
<view class="td cell-stock"><text class="td-txt">{{ item.stock }}</text></view>
<view class="td cell-audit"><text class="td-txt">{{ item.audit_status }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_show }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.is_show ? '显示' : '隐藏' }}</text>
</view>
</view>
<view class="td cell-op">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">详情</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<view class="pagination-footer">
<view class="page-total"><text class="total-txt">共 {{ productList.length }} 条</text></view>
<view class="page-select">
<view class="select-mock mini">
<text class="select-val">20条/页</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="page-btns">
<text class="p-btn disabled"></text>
<text class="p-btn active">1</text>
<text class="p-btn disabled"></text>
</view>
</view>
</view>
</template>
<!-- Adding View -->
<template v-else>
<view class="breadcrumb">
<text class="back-link" @click="isAdding = false"> 返回</text>
<text class="current-path">直播商品管理</text>
</view>
<view class="select-card border-shadow">
<view class="form-row">
<text class="f-label">选择商品:</text>
<view class="selected-list">
<view class="p-box" v-for="(p, index) in selectedList" :key="index">
<image class="p-thumb" :src="p.image" mode="aspectFill"></image>
<view class="remove-btn" @click="removeSelected(index)">×</view>
</view>
<view class="add-box" @click="handleAddProduct">
<text class="add-ic">👜</text>
</view>
</view>
</view>
<view class="form-row mt-24">
<button class="btn-generate" @click="handleGenerate">生成直播商品</button>
</view>
</view>
</template>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const isAdding = ref(false)
const selectedList = ref([
{ image: 'https://img0.baidu.com/it/u=3033502919,1657850259&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }
])
const productList = ref([
{
id: 92,
image: 'https://img0.baidu.com/it/u=3023224345,1529124233&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '绣球永生花网红干花花束大',
live_price: 149.00,
price: 149.00,
stock: 10617,
audit_status: '审核通过',
is_show: false
},
{
id: 89,
image: 'https://img1.baidu.com/it/u=3175865615,2002599723&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '家居梵高系列款软版盒袋',
live_price: 350.00,
price: 350.00,
stock: 8625,
audit_status: '审核通过',
is_show: false
},
{
id: 93,
image: 'https://img2.baidu.com/it/u=2719717192,3826027113&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '【LESHUCANGHU',
live_price: 300.00,
price: 300.00,
stock: 164,
audit_status: '审核通过',
is_show: false
},
{
id: 116,
image: 'https://img0.baidu.com/it/u=2257917711,1359654032&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '爱奇艺智能 奇遇LT01',
live_price: 1199.00,
price: 1199.00,
stock: 6287,
audit_status: '审核通过',
is_show: false
}
])
const toggleStatus = (item: any) => {
item.is_show = !item.is_show
uni.showToast({ title: '状态修改成功', icon: 'success' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '详情查看中', icon: 'none' })
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确定要删除该商品吗?',
success: (res) => {
if (res.confirm) {
productList.value = productList.value.filter(i => i.id !== item.id)
uni.showToast({ title: '删除成功' })
}
}
})
}
const handleAddProduct = () => {
uni.showToast({ title: '选择商品功能开发中', icon: 'none' })
}
const removeSelected = (index: number) => {
selectedList.value.splice(index, 1)
}
const handleGenerate = () => {
uni.showToast({ title: '生成成功', icon: 'success' })
isAdding.value = false
}
</script>
<style scoped lang="scss">
.marketing-live-product {
min-height: 100vh;
background: #f0f2f5;
padding: 16px;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.ml-16 { margin-left: 16px; }
.ml-24 { margin-left: 24px; }
.mt-24 { margin-top: 24px; }
/* 过滤栏 */
.filter-card {
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
.input-mock, .select-mock {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
font-size: 13px;
}
.input-mock { width: 300px; }
.select-mock { width: 160px; justify-content: space-between; }
.select-mock.mini { width: 100px; height: 28px; }
.select-val { font-size: 13px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
.btn-query {
width: 64px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 操作栏 */
.action-bar {
margin-bottom: 16px;
}
.btn-add {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 表格区域 */
.table-card {
padding: 24px;
}
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
/* 各列宽度 */
.cell-id { width: 70px; }
.cell-info { flex: 1; min-width: 250px; }
.cell-price { width: 100px; text-align: center; }
.cell-stock { width: 100px; text-align: center; }
.cell-audit { width: 120px; text-align: center; }
.cell-status { width: 100px; text-align: center; }
.cell-op { width: 120px; text-align: right; }
.info-wrap {
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
}
.thumb {
width: 40px;
height: 40px;
border-radius: 4px;
}
.p-title {
font-size: 13px;
color: #515a6e;
line-height: 1.4;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-links {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-split { color: #e8eaec; margin: 0 8px; }
/* Adding Template Styles */
.breadcrumb {
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.back-link { font-size: 14px; color: #8c8c8c; cursor: pointer; }
.current-path { font-size: 14px; color: #262626; font-weight: 600; }
.select-card {
padding: 48px;
}
.form-row {
display: flex;
flex-direction: row;
}
.f-label {
width: 100px;
font-size: 14px;
color: #262626;
}
.selected-list {
flex: 1;
display: flex;
flex-direction: row;
gap: 16px;
}
.p-box {
width: 64px;
height: 64px;
position: relative;
}
.p-thumb {
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid #f0f0f0;
}
.remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 16px;
height: 16px;
background-color: #bfbfbf;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.add-box {
width: 64px;
height: 64px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
cursor: pointer;
}
.add-ic { font-size: 24px; color: #bfbfbf; }
.btn-generate {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 100px;
}
/* 分页 */
.pagination-footer {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 13px; color: #606266; }
</style>

View File

@@ -1,27 +1,744 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-live-room">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">直播状态:</text>
<view class="select-mock">
<text class="select-val">全部</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item ml-24">
<text class="label">搜索:</text>
<input class="input-mock" placeholder="请输入直播间名称/ID/主播昵称/微信号" />
</view>
<button class="btn-query ml-16">查询</button>
</view>
</view>
</AdminLayout>
<view class="action-bar">
<button class="btn-add" @click="showDrawer = true">添加直播间</button>
<button class="btn-sync ml-16">同步直播间</button>
</view>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">直播间ID</view>
<view class="th cell-name">直播间名称</view>
<view class="th cell-nick">主播昵称</view>
<view class="th cell-wechat">主播微信号</view>
<view class="th cell-time">直播开始时间</view>
<view class="th cell-time">计划结束时间</view>
<view class="th cell-time">创建时间</view>
<view class="th cell-status">显示状态</view>
<view class="th cell-live-status">直播状态</view>
<view class="th cell-sort">排序</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in roomList" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-name"><text class="td-txt">{{ item.name }}</text></view>
<view class="td cell-nick"><text class="td-txt">{{ item.anchor_nick }}</text></view>
<view class="td cell-wechat"><text class="td-txt">{{ item.anchor_wechat }}</text></view>
<view class="td cell-time"><text class="td-txt-small">{{ item.start_time }}</text></view>
<view class="td cell-time"><text class="td-txt-small">{{ item.end_time }}</text></view>
<view class="td cell-time"><text class="td-txt-small">{{ item.create_time }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_show }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.is_show ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="td cell-live-status"><text class="td-txt">{{ item.live_status }}</text></view>
<view class="td cell-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td cell-op">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">详情</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<view class="pagination-footer">
<view class="page-total"><text class="total-txt">共 {{ roomList.length }} 条</text></view>
<view class="page-select">
<view class="select-mock mini">
<text class="select-val">20条/页</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="page-btns">
<text class="p-btn disabled"></text>
<text class="p-btn active">1</text>
<text class="p-btn disabled"></text>
</view>
</view>
</view>
<!-- Drawer Overlay -->
<view v-if="showDrawer || isAnimating" class="drawer-mask" :class="{ active: showDrawer }" @click="closeDrawer"></view>
<!-- Drawer Panel -->
<view class="drawer-panel" :class="{ active: showDrawer }">
<view class="drawer-header">
<view class="header-left">
<text class="back-btn" @click="closeDrawer"> 返回</text>
<text class="drawer-title">直播间管理</text>
</view>
</view>
<view class="drawer-content">
<view class="alert-info">
<text class="alert-txt">提示:必须前往微信小程序官方后台开通直播权限,关注【小程序直播】获知直播状态</text>
</view>
<view class="form-item">
<view class="form-label required">选择主播:</view>
<view class="select-mock full" @click="handleSelectAnchor">
<text class="select-val">{{ formData.anchor_nick || '请选择' }}</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="form-item">
<view class="form-label required">直播间名称:</view>
<view class="input-wrap">
<input class="form-input" v-model="formData.name" placeholder="请输入直播间名称" />
<text class="char-count">{{ formData.name.length }}/80</text>
</view>
</view>
<view class="form-item">
<view class="form-label required">背景图:</view>
<view class="upload-box" @click="handleUpload('background')">
<view class="upload-placeholder" v-if="!formData.background">
<text class="up-ic">🖼️</text>
</view>
<image v-else :src="formData.background" class="upload-preview" mode="aspectFill" />
<text class="up-tip blue-bg">尺寸1080*1920px</text>
</view>
</view>
<view class="form-item">
<view class="form-label required">分享图:</view>
<view class="upload-box" @click="handleUpload('share')">
<view class="upload-placeholder" v-if="!formData.share_img">
<text class="up-ic">🖼️</text>
</view>
<image v-else :src="formData.share_img" class="upload-preview" mode="aspectFill" />
<text class="up-tip">尺寸800*640px</text>
</view>
</view>
<view class="form-item">
<view class="form-label">联系电话:</view>
<view class="input-wrap">
<input class="form-input" v-model="formData.phone" placeholder="请输入主播联系电话" />
<text class="char-count">{{ formData.phone.length }}/11</text>
</view>
</view>
<view class="form-item">
<view class="form-label required">直播时间:</view>
<view class="date-range-mock" @click="handleOpenDatePicker">
<text class="calendar-ic">📅</text>
<text class="date-val">{{ formData.start_time || '开始日期' }} - {{ formData.end_time || '结束日期' }}</text>
</view>
</view>
<view class="form-item">
<view class="form-label">排序:</view>
<input class="form-input w-extra-small" type="number" v-model="formData.sort" />
</view>
<view class="form-item">
<view class="form-label">直播间类型:</view>
<view class="radio-group">
<view class="radio-item" @click="formData.type = 'phone'">
<view class="radio-circle" :class="{ active: formData.type === 'phone' }"></view>
<text class="radio-txt">手机直播</text>
</view>
</view>
</view>
<view class="form-item flex-row">
<view class="form-label">直播间点赞:</view>
<view class="switch-mock" :class="{ active: formData.like_enabled }" @click="formData.like_enabled = !formData.like_enabled">
<view class="switch-dot"></view>
<text class="switch-txt">{{ formData.like_enabled ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="form-item flex-row">
<view class="form-label">直播卖货:</view>
<view class="switch-mock" :class="{ active: formData.sale_enabled }" @click="formData.sale_enabled = !formData.sale_enabled">
<view class="switch-dot"></view>
<text class="switch-txt">{{ formData.sale_enabled ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="form-item flex-row">
<view class="form-label">直播间评论:</view>
<view class="switch-mock" :class="{ active: formData.comment_enabled }" @click="formData.comment_enabled = !formData.comment_enabled">
<view class="switch-dot"></view>
<text class="switch-txt">{{ formData.comment_enabled ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="form-actions-bottom">
<button class="btn-submit" @click="handleSubmit">提交</button>
</view>
</view>
</view>
</view>
</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')
const showDrawer = ref(false)
const isAnimating = ref(false)
const formData = ref({
anchor_nick: '',
name: '',
background: '',
share_img: '',
phone: '',
start_time: '',
end_time: '',
sort: 0,
type: 'phone',
like_enabled: true,
sale_enabled: true,
comment_enabled: true
})
const roomList = ref([
{
id: 88,
name: 'CRMEB 年中618活动开始',
anchor_nick: '打羽毛球',
anchor_wechat: 'evoxwht',
start_time: '2025-06-17 00:00:00',
end_time: '2025-06-18 00:00:00',
create_time: '2025-06-16 14:56:53',
is_show: true,
live_status: '已结束',
sort: 1
},
{
id: 90,
name: '123456789',
anchor_nick: '万万',
anchor_wechat: 'xiao112032014',
start_time: '2025-07-07 10:20:00',
end_time: '2025-07-07 12:00:00',
create_time: '2025-07-07 10:05:43',
is_show: true,
live_status: '已结束',
sort: 0
},
{
id: 89,
name: '测试1111111',
anchor_nick: '打羽毛球',
anchor_wechat: '',
start_time: '2025-05-20 14:50:00',
end_time: '2025-05-20 15:22:00',
create_time: '2025-06-17 10:03:08',
is_show: true,
live_status: '已结束',
sort: 0
},
{
id: 10,
name: '开学季,最后一天',
anchor_nick: '等风来',
anchor_wechat: 'welalnidaobel',
start_time: '2021-09-01 19:00:00',
end_time: '2021-09-01 20:00:00',
create_time: '2021-08-30 11:53:01',
is_show: false,
live_status: '已结束',
sort: 0
}
])
const toggleStatus = (item: any) => {
item.is_show = !item.is_show
uni.showToast({ title: '状态修改成功', icon: 'success' })
}
const handleEdit = (item: any) => {
formData.value = { ...item, like_enabled: true, sale_enabled: true, comment_enabled: true }
showDrawer.value = true
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确定要删除该直播间吗?',
success: (res) => {
if (res.confirm) {
roomList.value = roomList.value.filter(i => i.id !== item.id)
uni.showToast({ title: '删除成功' })
}
}
})
}
const handleSelectAnchor = () => {
uni.showToast({ title: '功能开发中', icon: 'none' })
}
const handleUpload = (type: string) => {
uni.chooseImage({
count: 1,
success: (res) => {
if (type === 'background') {
formData.value.background = res.tempFilePaths[0]
} else {
formData.value.share_img = res.tempFilePaths[0]
}
}
})
}
const handleOpenDatePicker = () => {
uni.showToast({ title: '日期选择功能开发中', icon: 'none' })
}
const handleSubmit = () => {
uni.showToast({ title: '提交成功', icon: 'success' })
closeDrawer()
}
const closeDrawer = () => {
showDrawer.value = false
isAnimating.value = true
setTimeout(() => {
isAnimating.value = false
}, 300)
}
</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; }
.marketing-live-room {
min-height: 100vh;
background: #f0f2f5;
padding: 16px;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.ml-16 { margin-left: 16px; }
.ml-24 { margin-left: 24px; }
.mt-16 { margin-top: 16px; }
/* 过滤栏 */
.filter-card {
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
.input-mock, .select-mock {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
font-size: 13px;
}
.input-mock { width: 300px; }
.select-mock { width: 160px; justify-content: space-between; }
.select-mock.mini { width: 100px; height: 28px; }
.select-mock.full { width: 100%; }
.select-val { font-size: 13px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
.btn-query {
width: 64px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 操作栏 */
.action-bar {
margin-bottom: 16px;
display: flex;
flex-direction: row;
}
.btn-add {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.btn-sync {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #fff;
color: #1890ff;
border: 1px solid #1890ff;
font-size: 14px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 表格区域 */
.table-card {
padding: 24px;
}
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
.td-txt-small { font-size: 12px; color: #808695; display: block; }
/* 各列宽度 */
.cell-id { width: 70px; }
.cell-name { flex: 1; min-width: 150px; }
.cell-nick { width: 120px; }
.cell-wechat { width: 120px; }
.cell-time { width: 150px; }
.cell-status { width: 100px; text-align: center; }
.cell-live-status { width: 100px; text-align: center; }
.cell-sort { width: 60px; text-align: center; }
.cell-op { width: 120px; text-align: right; }
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-links {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-split { color: #e8eaec; margin: 0 8px; }
/* 分页 */
.pagination-footer {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 13px; color: #606266; }
/* Drawer Styles */
.drawer-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.45);
z-index: 1000;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.drawer-mask.active {
opacity: 1;
pointer-events: auto;
}
.drawer-panel {
position: fixed;
top: 0;
right: -50%;
width: 50%;
height: 100%;
background-color: #fff;
z-index: 1001;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
transition: right 0.3s ease-out;
}
.drawer-panel.active {
right: 0;
}
.drawer-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
}
.header-left {
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
}
.back-btn {
font-size: 14px;
color: #8c8c8c;
cursor: pointer;
}
.drawer-title {
font-size: 16px;
font-weight: 600;
color: #262626;
}
.drawer-content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.alert-info {
background-color: #fff7e6;
border: 1px solid #ffe7ba;
padding: 12px 16px;
margin-bottom: 24px;
border-radius: 4px;
}
.alert-txt {
font-size: 13px;
color: #fa8c16;
}
.form-item {
margin-bottom: 24px;
}
.flex-row {
display: flex;
flex-direction: row;
align-items: center;
}
.form-label {
display: block;
font-size: 14px;
color: #262626;
margin-bottom: 8px;
width: 120px;
}
.flex-row .form-label { margin-bottom: 0; }
.required::before {
content: '*';
color: #ff4d4f;
margin-right: 4px;
}
.input-wrap {
position: relative;
width: 100%;
}
.form-input {
width: 100%;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 40px 0 12px;
font-size: 14px;
}
.w-extra-small { width: 80px; }
.char-count {
position: absolute;
right: 12px;
top: 6px;
font-size: 12px;
color: #bfbfbf;
}
.upload-box {
display: flex;
flex-direction: row;
align-items: flex-end;
gap: 12px;
}
.upload-placeholder {
width: 80px;
height: 80px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
}
.up-ic { font-size: 24px; color: #bfbfbf; }
.up-tip {
font-size: 12px;
color: #1890ff;
background-color: #e6f7ff;
border: 1px solid #91d5ff;
padding: 2px 8px;
border-radius: 2px;
}
.up-tip.blue-bg {
background-color: #1890ff;
color: #fff;
border: none;
}
.date-range-mock {
width: 100%;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
gap: 8px;
}
.calendar-ic { font-size: 14px; color: #bfbfbf; }
.date-val { font-size: 14px; color: #bfbfbf; }
.radio-group {
display: flex;
flex-direction: row;
gap: 24px;
}
.radio-item {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.radio-circle {
width: 16px;
height: 16px;
border: 1px solid #d9d9d9;
border-radius: 50%;
position: relative;
}
.radio-circle.active { border-color: #1890ff; }
.radio-circle.active::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 8px;
height: 8px;
background-color: #1890ff;
border-radius: 50%;
}
.radio-txt { font-size: 14px; color: #262626; }
.form-actions-bottom {
margin-top: 40px;
}
.btn-submit {
width: 64px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>

View File

@@ -1,27 +1,343 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-member-card">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">批次搜索:</text>
<input class="input-mock" placeholder="请输入批次名" />
</view>
<view class="filter-item">
<text class="label">是否开启:</text>
<view class="select-mock">
<text class="select-val">全部</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="btn-group">
<button class="btn btn-search">查询</button>
<button class="btn btn-reset">重置</button>
</view>
</view>
<view class="action-row">
<button class="btn btn-primary" @click="showAddBatch = true">+ 添加批次</button>
</view>
</view>
</AdminLayout>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-name">批次名称</view>
<view class="th cell-num">体验卡数量</view>
<view class="th cell-type">会员类型</view>
<view class="th cell-time">生效时间</view>
<view class="th cell-status">是否启用</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in cards" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-name"><text class="td-txt">{{ item.title }}</text></view>
<view class="td cell-num"><text class="td-txt">{{ item.use_num }}/{{ item.total_num }}</text></view>
<view class="td cell-type"><text class="td-txt">{{ item.member_type }}</text></view>
<view class="td cell-time"><text class="td-txt">{{ item.create_time }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.status }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.status ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="td cell-op">
<text class="op-link" @click="showQrCode(item)">二维码</text>
<text class="op-link ml-10" @click="viewDetails(item)">详情</text>
</view>
</view>
</view>
</view>
</view>
<!-- 添加批次弹窗 -->
<view v-if="showAddBatch" class="modal-mask">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">添加批次</text>
<text class="modal-close" @click="showAddBatch = false">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">批次名称:</text>
<input class="form-input" placeholder="请输入批次名称" />
</view>
<view class="form-item">
<text class="form-label">导入数量:</text>
<input class="form-input" type="number" placeholder="请输入生成数量" />
</view>
<view class="form-item">
<text class="form-label">会员类型:</text>
<view class="form-select">
<text>请选择会员类型</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="form-item">
<text class="form-label">备注:</text>
<textarea class="form-textarea" placeholder="请输入备注"></textarea>
</view>
</view>
<view class="modal-footer">
<button class="btn btn-cancel" @click="showAddBatch = false">取消</button>
<button class="btn btn-submit" @click="handleAddSubmit">提交</button>
</view>
</view>
</view>
<!-- 二维码弹窗 -->
<view v-if="showQrModal" class="modal-mask">
<view class="modal-content qr-modal">
<view class="modal-header">
<text class="modal-title">预览体验卡二维码</text>
<text class="modal-close" @click="showQrModal = false">×</text>
</view>
<view class="modal-body qr-body">
<image class="qr-img" src="https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png" mode="aspectFit"></image>
<text class="qr-tips">请扫码体验会员卡</text>
</view>
</view>
</view>
</view>
</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')
const showAddBatch = ref(false)
const showQrModal = ref(false)
const cards = ref([
{ id: 4, title: '双11体验卡', use_num: 1, total_num: 100, member_type: '年卡会员', create_time: '2023-11-01 12:00:00', status: true },
{ id: 3, title: '新人体验卷', use_num: 50, total_num: 200, member_type: '月卡会员', create_time: '2023-10-25 09:30:00', status: true },
{ id: 2, title: '测试批次', use_num: 0, total_num: 10, member_type: '季卡会员', create_time: '2023-10-20 15:45:00', status: false }
])
const toggleStatus = (item: any) => {
item.status = !item.status
uni.showToast({ title: '操作成功', icon: 'success' })
}
const showQrCode = (item: any) => {
showQrModal.value = true
}
const viewDetails = (item: any) => {
uni.showToast({ title: '查看详情: ' + item.title, icon: 'none' })
}
const handleAddSubmit = () => {
showAddBatch.value = false
uni.showToast({ title: '添加成功', icon: 'success' })
}
</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; }
.marketing-member-card {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.filter-card {
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 24px;
margin-bottom: 16px;
}
.label {
font-size: 14px;
color: #333;
width: 80px;
}
.input-mock {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.select-mock {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.select-val { font-size: 14px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
.btn-group {
display: flex;
flex-direction: row;
margin-bottom: 16px;
}
.btn {
height: 32px;
line-height: 32px;
padding: 0 20px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
margin-right: 8px;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
}
.btn-primary { background: #1890ff; color: #fff; border: none; }
.btn-search { background: #1890ff; color: #fff; border: none; }
.btn-reset { margin-left: 8px; }
.action-row {
margin-top: 8px;
}
.table-card { padding: 24px; }
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th { padding: 12px 8px; font-size: 13px; color: #515a6e; font-weight: bold; }
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td { padding: 16px 8px; }
.td-txt { font-size: 13px; color: #515a6e; }
.cell-id { width: 60px; }
.cell-name { flex: 1; }
.cell-num { width: 120px; }
.cell-type { width: 120px; }
.cell-time { width: 160px; }
.cell-status { width: 100px; }
.cell-op { width: 120px; text-align: right; }
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.ml-10 { margin-left: 10px; }
.switch-mock {
width: 44px;
height: 22px;
background-color: #bfbfbf;
border-radius: 11px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 14px;
height: 14px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 26px; }
.switch-txt { font-size: 10px; color: #fff; margin-left: 18px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
/* Modal Styles */
.modal-mask {
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 {
width: 500px;
background: #fff;
border-radius: 4px;
overflow: hidden;
}
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid #e8eaec;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.modal-title { font-size: 16px; font-weight: bold; color: #17233d; }
.modal-close { font-size: 24px; color: #909399; cursor: pointer; }
.modal-body { padding: 24px; }
.modal-footer {
padding: 12px 24px;
border-top: 1px solid #e8eaec;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.form-item {
display: flex;
flex-direction: row;
margin-bottom: 20px;
align-items: flex-start;
}
.form-label { width: 100px; font-size: 14px; color: #606266; padding-top: 6px; }
.form-input { flex: 1; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; }
.form-select { flex: 1; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; color: #c0c4cc; font-size: 14px; }
.form-textarea { flex: 1; height: 80px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 8px 12px; }
.btn-cancel { margin-right: 8px; }
.btn-submit { background: #1890ff; color: #fff; border: none; }
.qr-modal { width: 300px; }
.qr-body { display: flex; flex-direction: column; align-items: center; }
.qr-img { width: 200px; height: 200px; margin-bottom: 16px; }
.qr-tips { font-size: 14px; color: #666; }
</style>

View File

@@ -1,27 +1,225 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-member-config">
<view class="config-card border-shadow">
<view class="config-header">
<text class="config-title">会员基础配置</text>
</view>
<view class="config-body">
<view class="config-item">
<view class="item-label">
<text class="label-txt">是否开启付费会员:</text>
<text class="label-desc">关闭之后,商城将不再展示付费会员相关功能</text>
</view>
<view class="item-content">
<view class="switch-mock" :class="{ active: config.is_open }" @click="config.is_open = !config.is_open">
<view class="switch-dot"></view>
<text class="switch-txt">{{ config.is_open ? '开启' : '关闭' }}</text>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">会员期内背景图:</text>
<text class="label-desc">建议尺寸: 700*320px</text>
</view>
<view class="item-content">
<view class="upload-box" @click="handleUpload('bg')">
<image v-if="config.bg_img" :src="config.bg_img" class="preview-img"></image>
<view v-else class="upload-placeholder">
<text class="plus">+</text>
<text class="upload-txt">选择图片</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">会员到期背景图:</text>
<text class="label-desc">建议尺寸: 700*320px</text>
</view>
<view class="item-content">
<view class="upload-box" @click="handleUpload('expire_bg')">
<image v-if="config.expire_bg_img" :src="config.expire_bg_img" class="preview-img"></image>
<view v-else class="upload-placeholder">
<text class="plus">+</text>
<text class="upload-txt">选择图片</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">会员规则说明:</text>
<text class="label-desc">在会员中心页面展示的规则说明</text>
</view>
<view class="item-content flex-1">
<textarea class="config-textarea" v-model="config.rules" placeholder="请输入会员规则说明"></textarea>
</view>
</view>
<view class="config-footer">
<button class="btn btn-primary" @click="handleSave">保存配置</button>
</view>
</view>
</view>
</AdminLayout>
</view>
</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')
import { ref, reactive } from 'vue'
const config = reactive({
is_open: true,
bg_img: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png',
expire_bg_img: '',
rules: '1. 会员有效期自购买之日起计算\n2. 会员权益仅限本人使用\n3. 会员卡一经售出,概不退换'
})
const handleUpload = (type: string) => {
uni.showToast({ title: '文件管理器暂未开启', icon: 'none' })
}
const handleSave = () => {
uni.showToast({ title: '保存成功', icon: 'success' })
}
</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; }
.marketing-member-config {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.config-card {
padding: 24px;
}
.config-header {
border-bottom: 1px solid #e8eaec;
padding-bottom: 16px;
margin-bottom: 24px;
}
.config-title {
font-size: 16px;
font-weight: bold;
color: #17233d;
}
.config-item {
display: flex;
flex-direction: row;
margin-bottom: 30px;
align-items: flex-start;
}
.item-label {
width: 200px;
display: flex;
flex-direction: column;
}
.label-txt {
font-size: 14px;
color: #333;
margin-bottom: 4px;
}
.label-desc {
font-size: 12px;
color: #999;
}
.item-content {
flex: 1;
}
.switch-mock {
width: 44px;
height: 22px;
background-color: #bfbfbf;
border-radius: 11px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 14px;
height: 14px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 26px; }
.switch-txt { font-size: 10px; color: #fff; margin-left: 18px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.upload-box {
width: 120px;
height: 80px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
}
.plus { font-size: 24px; color: #999; }
.upload-txt { font-size: 12px; color: #999; }
.preview-img { width: 100%; height: 100%; object-fit: cover; }
.config-textarea {
width: 100%;
max-width: 600px;
height: 120px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 12px;
font-size: 14px;
}
.config-footer {
margin-top: 40px;
padding-left: 200px;
}
.btn-primary {
width: 120px;
height: 36px;
line-height: 36px;
background: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.flex-1 { flex: 1; }
</style>

View File

@@ -1,27 +1,187 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-member-record">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">会员名:</text>
<input class="input-mock" placeholder="请输入会员名" />
</view>
<view class="filter-item">
<text class="label">支付方式:</text>
<view class="select-mock">
<text class="select-val">全部</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="btn-group">
<button class="btn btn-search">查询</button>
<button class="btn btn-reset">重置</button>
</view>
</view>
</view>
</AdminLayout>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-user">用户信息</view>
<view class="th cell-type">会员类型</view>
<view class="th cell-price">支付金额</view>
<view class="th cell-pay">支付方式</view>
<view class="th cell-time">购买时间</view>
<view class="th cell-expire">过期时间</view>
</view>
<view class="table-body">
<view v-for="item in records" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-user">
<view class="user-box">
<image class="avatar" :src="item.avatar"></image>
<text class="nickname">{{ item.nickname }}</text>
</view>
</view>
<view class="td cell-type"><text class="td-txt">{{ item.member_type }}</text></view>
<view class="td cell-price"><text class="td-txt">¥{{ item.price.toFixed(2) }}</text></view>
<view class="td cell-pay"><text class="td-txt">{{ item.pay_type }}</text></view>
<view class="td cell-time"><text class="td-txt">{{ item.create_time }}</text></view>
<view class="td cell-expire"><text class="td-txt">{{ item.expire_time }}</text></view>
</view>
</view>
</view>
</view>
</view>
</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')
const records = ref([
{ id: 10, avatar: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png', nickname: '张三', member_type: '年卡会员', price: 99.00, pay_type: '微信支付', create_time: '2023-11-20 10:00:00', expire_time: '2024-11-20 10:00:00' },
{ id: 9, avatar: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png', nickname: '李四', member_type: '月卡会员', price: 9.90, pay_type: '余额支付', create_time: '2023-11-19 15:30:00', expire_time: '2023-12-19 15:30:00' },
{ id: 8, avatar: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png', nickname: '王五', member_type: '体验会员', price: 0.00, pay_type: '激活码', create_time: '2023-11-18 09:20:00', expire_time: '2023-11-25 09:20:00' }
])
</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; }
.marketing-member-record {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.filter-card {
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 24px;
}
.label {
font-size: 14px;
color: #333;
width: 70px;
}
.input-mock {
width: 180px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.select-mock {
width: 180px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.select-val { font-size: 14px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
.btn-group {
display: flex;
flex-direction: row;
}
.btn {
height: 32px;
line-height: 32px;
padding: 0 20px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
margin-right: 8px;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
}
.btn-search { background: #1890ff; color: #fff; border: none; }
.table-card { padding: 24px; }
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th { padding: 12px 8px; font-size: 13px; color: #515a6e; font-weight: bold; }
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td { padding: 16px 8px; }
.td-txt { font-size: 13px; color: #515a6e; }
.cell-id { width: 60px; }
.cell-user { flex: 1; }
.cell-type { width: 120px; }
.cell-price { width: 100px; }
.cell-pay { width: 100px; }
.cell-time { width: 160px; }
.cell-expire { width: 160px; }
.user-box {
display: flex;
flex-direction: row;
align-items: center;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 16px;
margin-right: 8px;
}
.nickname { font-size: 13px; color: #515a6e; }
</style>

View File

@@ -0,0 +1,142 @@
<template>
<view class="marketing-member-right">
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-icon">权益图标</view>
<view class="th cell-name">权益名称</view>
<view class="th cell-desc">权益简介</view>
<view class="th cell-status">是否展示</view>
<view class="th cell-sort">排序</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in memberRights" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-icon">
<image class="right-icon" :src="item.icon" mode="aspectFit"></image>
</view>
<view class="td cell-name"><text class="td-txt">{{ item.name }}</text></view>
<view class="td cell-desc"><text class="td-txt">{{ item.desc }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_show }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.is_show ? '显示' : '隐藏' }}</text>
</view>
</view>
<view class="td cell-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td cell-op">
<text class="op-link" @click="handleEdit(item)">编辑</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const memberRights = ref([
{ id: 9, icon: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png', name: '运费券', desc: '每月领取运费券', is_show: true, sort: 10 },
{ id: 8, icon: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png', name: '充值优惠', desc: '充值立减优惠', is_show: true, sort: 8 },
{ id: 7, icon: 'https://demo26.crmeb.net/uploads/attach/2021/11/20211115/a6f3b06e9d6d5a1b3c9d6d5a1b3c9d6d.png', name: '积分翻倍', desc: '购物获取双倍积分', is_show: true, sort: 7 }
])
const toggleStatus = (item: any) => {
item.is_show = !item.is_show
uni.showToast({ title: '修改成功', icon: 'success' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '编辑功能开发中', icon: 'none' })
}
</script>
<style scoped lang="scss">
.marketing-member-right {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.table-card { padding: 24px; }
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td { padding: 16px 8px; }
.td-txt { font-size: 13px; color: #515a6e; }
.cell-id { width: 80px; }
.cell-icon { width: 100px; text-align: center; }
.cell-name { width: 150px; }
.cell-desc { flex: 1; }
.cell-status { width: 120px; text-align: center; }
.cell-sort { width: 100px; text-align: center; }
.cell-op { width: 80px; text-align: right; }
.right-icon {
width: 40px;
height: 40px;
}
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
</style>

View File

@@ -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>('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>

View File

@@ -1,27 +1,147 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-member-type">
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-name">会员名</view>
<view class="th cell-days">有效期(天)</view>
<view class="th cell-price">原价</view>
<view class="th cell-discount">优惠价</view>
<view class="th cell-status">是否开启</view>
<view class="th cell-sort">排序</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in memberTypes" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-name"><text class="td-txt">{{ item.name }}</text></view>
<view class="td cell-days"><text class="td-txt">{{ item.days }}</text></view>
<view class="td cell-price"><text class="td-txt">¥{{ item.price.toFixed(2) }}</text></view>
<view class="td cell-discount"><text class="td-txt">¥{{ item.discount.toFixed(2) }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_open }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.is_open ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="td cell-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td cell-op">
<text class="op-link" @click="handleEdit(item)">编辑</text>
</view>
</view>
</view>
</view>
</view>
</AdminLayout>
</view>
</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')
const memberTypes = ref([
{ id: 5, name: '44555', days: '12', price: 69.00, discount: 0.00, is_open: true, sort: 55 },
{ id: 4, name: '5566', days: '永久', price: 1080.00, discount: 1080.00, is_open: true, sort: 5 },
{ id: 3, name: '年卡', days: '365', price: 99.00, discount: 0.01, is_open: true, sort: 5 },
{ id: 2, name: '55', days: '90', price: 699.00, discount: 499.00, is_open: true, sort: 5 },
{ id: 1, name: '55', days: '30', price: 699.00, discount: 499.00, is_open: true, sort: 5 }
])
const toggleStatus = (item: any) => {
item.is_open = !item.is_open
uni.showToast({ title: '修改成功', icon: 'success' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '编辑功能开发中', icon: 'none' })
}
</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; }
.marketing-member-type {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.table-card {
padding: 24px;
}
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
/* 各列宽度 */
.cell-id { width: 80px; }
.cell-name { flex: 1; min-width: 150px; }
.cell-days { width: 120px; }
.cell-price { width: 120px; }
.cell-discount { width: 120px; }
.cell-status { width: 120px; text-align: center; }
.cell-sort { width: 100px; text-align: center; }
.cell-op { width: 80px; text-align: right; }
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
</style>

View File

@@ -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>('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>

View File

@@ -0,0 +1,521 @@
<template>
<view class="admin-main">
<view class="card-container">
<view class="card-header">
<text class="card-header-title">新人礼设置</text>
</view>
<view class="form-content">
<!-- 赠送余额 -->
<view class="form-item">
<view class="label-col">
<text class="form-label">赠送余额(元):</text>
</view>
<view class="input-col">
<input type="number" class="form-input" v-model="formData.balance" />
<text class="form-tip">新用户奖励金额必须大于等于00为不赠送</text>
</view>
</view>
<!-- 赠送积分 -->
<view class="form-item">
<view class="label-col">
<text class="form-label">赠送积分:</text>
</view>
<view class="input-col">
<input type="number" class="form-input" v-model="formData.integral" />
<text class="form-tip">新用户奖励积分必须大于等于00为不赠送</text>
</view>
</view>
<!-- 赠送优惠券 -->
<view class="form-item row-center">
<view class="label-col">
<text class="form-label">赠送优惠券:</text>
</view>
<view class="input-col row-layout">
<view class="coupon-display-area" v-if="formData.coupons.length > 0">
<view v-for="(coupon, index) in formData.coupons" :key="index" class="coupon-tag-group">
<view class="coupon-tag-main">
<text class="coupon-tag-name">{{ coupon.name }}</text>
<text class="coupon-tag-del" @click="removeCoupon(index)">×</text>
</view>
<view class="explicit-edit-btn" @click="editCoupon(index)">
<text class="edit-btn-text">修改设置</text>
</view>
</view>
</view>
<button class="btn-select-action" @click="showCouponModal = true">选择优惠券</button>
</view>
</view>
<!-- 确认按钮 -->
<view class="form-submit-bar">
<button class="btn-primary-confirm" @click="handleSubmit">确认</button>
</view>
</view>
</view>
<!-- 优惠券选择与详情配置弹窗 -->
<view class="modal-mask" v-if="showCouponModal" @click="closeModal">
<view class="modal-box" @click.stop>
<view class="modal-head">
<text class="modal-head-title">{{ isEditing ? '配置赠送详情' : '选择优惠券' }}</text>
<text class="modal-head-close" @click="closeModal">×</text>
</view>
<view class="modal-body">
<!-- 编辑/设置模式:当用户由于点击“修改设置”时触发 -->
<view v-if="isEditing" class="setting-form">
<view class="setting-row">
<text class="setting-label">显示名称:</text>
<input class="setting-input" v-model="editingCoupon.name" placeholder="请输入页面显示的名称" />
</view>
<view class="setting-row">
<text class="setting-label">发放描述:</text>
<input class="setting-input" v-model="editingCoupon.desc" placeholder="请输入发放时的描述" />
</view>
<view class="setting-tip">
<text class="tip-text">* 此处的修改仅影响“新人礼”活动中的展示,不影响优惠券自身配置</text>
</view>
</view>
<!-- 选择模式 -->
<view v-else class="selection-list">
<view v-for="(item, index) in couponOptions" :key="index"
class="selection-card" :class="{'selected-card': isSelected(item)}"
@click="toggleCoupon(item)">
<view class="card-left">
<text class="card-name">{{ item.name }}</text>
<text class="card-desc">{{ item.desc }}</text>
</view>
<view class="card-right">
<view class="check-circle" :class="{'checked-circle': isSelected(item)}">
<text class="check-mark" v-if="isSelected(item)">✓</text>
</view>
</view>
</view>
</view>
</view>
<view class="modal-foot">
<button class="foot-btn-cancel" @click="closeModal">取消</button>
<button class="foot-btn-ok" @click="confirmModal">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
interface Coupon {
id: number;
name: string;
desc: string;
}
const formData = reactive({
balance: '88888',
integral: '88888',
coupons: [] as Coupon[]
})
const showCouponModal = ref(false)
const isEditing = ref(false)
const editingIndex = ref(-1)
const editingCoupon = reactive<Coupon>({ id: 0, name: '', desc: '' })
const couponOptions = reactive<Coupon[]>([
{ id: 1, name: '满100减10元券', desc: '全场通用' },
{ id: 2, name: '新人5元无门槛', desc: '仅限新人使用' },
{ id: 3, name: '满200减50元券', desc: '限特定商品' }
])
function isSelected(item: Coupon): boolean {
return formData.coupons.some(c => c.id === item.id)
}
function toggleCoupon(item: Coupon) {
const index = formData.coupons.findIndex(c => c.id === item.id)
if (index > -1) {
formData.coupons.splice(index, 1)
} else {
formData.coupons.push({ ...item })
}
}
function removeCoupon(index: number) {
formData.coupons.splice(index, 1)
}
function editCoupon(index: number) {
editingIndex.value = index
const coupon = formData.coupons[index]
editingCoupon.id = coupon.id
editingCoupon.name = coupon.name
editingCoupon.desc = coupon.desc
isEditing.value = true
showCouponModal.value = true
}
function closeModal() {
showCouponModal.value = false
isEditing.value = false
}
function confirmModal() {
if (isEditing.value && editingIndex.value > -1) {
formData.coupons[editingIndex.value].name = editingCoupon.name
formData.coupons[editingIndex.value].desc = editingCoupon.desc
}
closeModal()
}
function handleSubmit() {
uni.showLoading({ title: '保存中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '设置已生效',
icon: 'success'
})
}, 500)
}
</script>
<style scoped lang="scss">
.admin-main {
padding: 24px;
background-color: #f0f2f5;
min-height: 100vh;
}
.card-container {
background-color: #fff;
border-radius: 2px;
}
.card-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
}
.card-header-title {
font-size: 16px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.form-content {
padding: 40px 60px;
}
.form-item {
display: flex;
flex-direction: row;
margin-bottom: 24px;
}
.row-center {
align-items: flex-start;
}
.label-col {
width: 140px;
padding-right: 12px;
text-align: right;
}
.form-label {
font-size: 14px;
color: #333;
line-height: 40px;
}
.input-col {
flex: 1;
}
.row-layout {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
.form-input {
width: 320px;
height: 40px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
transition: all 0.3s;
}
.form-input:focus {
border-color: #1890ff;
}
.form-tip {
display: block;
margin-top: 10px;
font-size: 14px;
color: #bfbfbf;
}
.coupon-display-area {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.coupon-tag-group {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 16px;
margin-bottom: 8px;
}
.coupon-tag-main {
display: flex;
flex-direction: row;
align-items: center;
background-color: #f5f5f5;
border: 1px solid #d9d9d9;
padding: 0 10px;
height: 32px;
border-radius: 4px;
}
.coupon-tag-name {
font-size: 14px;
color: #555;
}
.coupon-tag-del {
margin-left: 8px;
font-size: 16px;
color: #999;
cursor: pointer;
}
.explicit-edit-btn {
margin-left: 8px;
cursor: pointer;
}
.edit-btn-text {
font-size: 13px;
color: #1890ff;
text-decoration: underline;
}
.btn-select-action {
height: 36px;
line-height: 36px;
padding: 0 16px;
font-size: 14px;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
color: #666;
margin-left: 0;
cursor: pointer;
}
.form-submit-bar {
margin-top: 32px;
padding-left: 140px;
}
.btn-primary-confirm {
width: 88px;
height: 38px;
line-height: 38px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border-radius: 4px;
margin-left: 0;
border: none;
cursor: pointer;
}
/* Modal */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-box {
width: 560px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
overflow: hidden;
}
.modal-head {
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.modal-head-title {
font-size: 18px;
font-weight: 500;
color: #222;
}
.modal-head-close {
font-size: 24px;
color: #aaa;
cursor: pointer;
}
.modal-body {
padding: 24px;
max-height: 450px;
overflow-y: auto;
}
.setting-form {
padding: 10px 0;
}
.setting-row {
margin-bottom: 24px;
}
.setting-label {
display: block;
margin-bottom: 10px;
font-size: 14px;
color: #333;
font-weight: 500;
}
.setting-input {
width: 100%;
height: 42px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.setting-tip {
margin-top: 16px;
}
.tip-text {
font-size: 12px;
color: #ff4d4f;
}
.selection-list {
display: flex;
flex-direction: column;
}
.selection-card {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border: 1px solid #f0f0f0;
margin-bottom: 14px;
border-radius: 6px;
transition: all 0.2s;
cursor: pointer;
}
.selection-card:hover {
border-color: #1890ff;
}
.selected-card {
border-color: #1890ff;
background-color: #f0faff;
}
.card-name {
font-size: 15px;
color: #333;
font-weight: 500;
}
.card-desc {
font-size: 13px;
color: #888;
margin-top: 6px;
}
.check-circle {
width: 20px;
height: 20px;
border: 1px solid #d9d9d9;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.checked-circle {
background-color: #1890ff;
border-color: #1890ff;
}
.check-mark {
color: #fff;
font-size: 12px;
}
.modal-foot {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.foot-btn-cancel, .foot-btn-ok {
height: 34px;
line-height: 34px;
padding: 0 20px;
margin-left: 12px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
}
.foot-btn-cancel {
background-color: #fff;
border: 1px solid #d9d9d9;
color: #666;
}
.foot-btn-ok {
background-color: #1890ff;
color: #fff;
border: none;
}
</style>

View File

@@ -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>('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>

View File

@@ -1,27 +1,187 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-recharge-config">
<view class="config-card border-shadow">
<view class="config-header">
<text class="config-title">用户充值配置</text>
</view>
<view class="config-body">
<view class="config-item">
<view class="item-label">
<text class="label-txt">余额功能启用:</text>
<text class="label-desc">商城余额功能启用或者关闭</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.balance_enabled = true">
<view class="radio-circle" :class="{ checked: config.balance_enabled }"></view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item ml-20" @click="config.balance_enabled = false">
<view class="radio-circle" :class="{ checked: !config.balance_enabled }"></view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">充值注意事项:</text>
<text class="label-desc">充值注意事项</text>
</view>
<view class="item-content">
<textarea class="config-textarea" v-model="config.notice" placeholder="请输入充值注意事项"></textarea>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">小程序充值开关:</text>
<text class="label-desc">仅小程序端的充值开关,小程序提交审核前,需要关闭此功能</text>
</view>
<view class="item-content">
<view class="radio-group">
<view class="radio-item" @click="config.mp_recharge = true">
<view class="radio-circle" :class="{ checked: config.mp_recharge }"></view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item ml-20" @click="config.mp_recharge = false">
<view class="radio-circle" :class="{ checked: !config.mp_recharge }"></view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="config-item">
<view class="item-label">
<text class="label-txt">最低充值金额:</text>
<text class="label-desc">用户单次最低充值金额</text>
</view>
<view class="item-content">
<input class="config-input" type="number" v-model="config.min_amount" />
</view>
</view>
<view class="config-footer">
<button class="btn-submit" @click="handleSave">提交</button>
</view>
</view>
</view>
</AdminLayout>
</view>
</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')
import { reactive } from 'vue'
const config = reactive({
balance_enabled: true,
notice: '充值后账户的金额不能提现,可用于商城消费使用\n佣金导入账户之后不能再次导出、不可提现\n账户充值出现问题可联系商城客服',
mp_recharge: false,
min_amount: 0.01
})
const handleSave = () => {
uni.showToast({ title: '保存成功', icon: 'success' })
}
</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; }
.marketing-recharge-config {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.config-card { padding: 24px; }
.config-header {
border-bottom: 1px solid #e8eaec;
padding-bottom: 16px;
margin-bottom: 24px;
}
.config-title {
font-size: 16px;
font-weight: bold;
color: #17233d;
position: relative;
padding-left: 12px;
}
.config-title::before {
content: '';
position: absolute;
left: 0;
top: 4px;
bottom: 4px;
width: 3px;
background: #1890ff;
}
.config-item {
display: flex;
flex-direction: row;
margin-bottom: 30px;
align-items: flex-start;
}
.item-label { width: 220px; display: flex; flex-direction: column; }
.label-txt { font-size: 14px; color: #333; margin-bottom: 4px; }
.label-desc { font-size: 12px; color: #999; }
.item-content { flex: 1; }
.radio-group { display: flex; flex-direction: row; padding-top: 4px; }
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
.radio-circle { width: 14px; height: 14px; border: 1px solid #dcdfe6; border-radius: 50%; margin-right: 6px; position: relative; }
.radio-circle.checked { border-color: #1890ff; }
.radio-circle.checked::after { content: ''; position: absolute; width: 8px; height: 8px; background: #1890ff; border-radius: 50%; top: 2px; left: 2px; }
.radio-txt { font-size: 14px; color: #606266; }
.ml-20 { margin-left: 20px; }
.config-textarea {
width: 100%;
max-width: 600px;
height: 120px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 12px;
font-size: 14px;
}
.config-input {
width: 300px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.config-footer {
margin-top: 40px;
padding-left: 220px;
}
.btn-submit {
width: 80px;
height: 32px;
line-height: 32px;
background: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,383 @@
<template>
<view class="marketing-recharge-quota">
<view class="content-layout">
<!-- 左侧预览 -->
<view class="preview-side">
<view class="phone-mock">
<view class="phone-header">
<text class="balance-label">我的余额</text>
<view class="balance-val">
<text class="symbol">¥</text>
<text class="num">0.00</text>
</view>
</view>
<view class="recharge-tabs">
<view class="tab active"><text class="tab-txt">账户充值</text></view>
<view class="tab"><text class="tab-txt">佣金导入</text></view>
</view>
<view class="quota-grid">
<view v-for="(item, index) in list" :key="index" class="quota-item" :class="{ active: index === 0 }">
<text class="price">{{ item.price }}元</text>
<text class="bonus">赠送{{ item.bonus }}元</text>
</view>
<view class="quota-item other">
<text class="other-txt">其他</text>
</view>
</view>
<view class="notice-section">
<text class="notice-title">注意事项:</text>
<text class="notice-content">充值后账户的金额不能提现可用于商城消费使用。佣金导入账户之后不能再次导出、不可提现。账户充值出现问题可联系商城客服也可拨打商城客服热线4008888888。</text>
</view>
<button class="recharge-btn">立即充值</button>
</view>
</view>
<!-- 右侧设置 -->
<view class="table-side border-shadow">
<view class="side-header">
<text class="side-title">充值金额设置</text>
</view>
<view class="action-row">
<button class="btn-primary" @click="showAddModal = true">添加数据</button>
</view>
<view class="table-container">
<view class="table-head">
<view class="th cell-id">编号</view>
<view class="th cell-price">售价</view>
<view class="th cell-bonus">赠送</view>
<view class="th cell-status">是否可用</view>
<view class="th cell-sort">排序</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in list" :key="item.id" class="table-row">
<view class="td cell-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td cell-price"><text class="td-txt">{{ item.price }}</text></view>
<view class="td cell-bonus"><text class="td-txt">{{ item.bonus }}</text></view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.is_open }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
</view>
</view>
<view class="td cell-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td cell-op">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-link del ml-10" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 添加数据弹窗 -->
<view v-if="showAddModal" class="modal-mask">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">添加数据</text>
<text class="modal-close" @click="showAddModal = false">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">售价:</text>
<input class="form-input" v-model="formData.price" type="number" placeholder="请输入售价" />
</view>
<view class="form-item">
<text class="form-label">赠送:</text>
<input class="form-input" v-model="formData.bonus" type="number" placeholder="请输入赠送" />
</view>
<view class="form-item">
<text class="form-label">排序:</text>
<input class="form-input" v-model="formData.sort" type="number" />
</view>
<view class="form-item">
<text class="form-label">状态:</text>
<view class="radio-group">
<view class="radio-item" @click="formData.is_open = true">
<view class="radio-circle" :class="{ checked: formData.is_open }"></view>
<text class="radio-txt">显示</text>
</view>
<view class="radio-item ml-20" @click="formData.is_open = false">
<view class="radio-circle" :class="{ checked: !formData.is_open }"></view>
<text class="radio-txt">隐藏</text>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn-cancel" @click="showAddModal = false">取消</button>
<button class="btn-submit" @click="handleSubmit">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
const showAddModal = ref(false)
const formData = reactive({
price: '',
bonus: '',
sort: 1,
is_open: true
})
const list = ref([
{ id: 640, price: 10, bonus: 2, is_open: true, sort: 6 },
{ id: 641, price: 20, bonus: 8, is_open: true, sort: 5 },
{ id: 642, price: 50, bonus: 20, is_open: true, sort: 4 },
{ id: 643, price: 100, bonus: 50, is_open: true, sort: 3 },
{ id: 644, price: 200, bonus: 110, is_open: true, sort: 2 },
{ id: 645, price: 300, bonus: 200, is_open: true, sort: 1 }
])
const toggleStatus = (item: any) => {
item.is_open = !item.is_open
uni.showToast({ title: '操作成功', icon: 'success' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '编辑功能', icon: 'none' })
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确认删除该项吗?',
success: (res) => {
if (res.confirm) {
const index = list.value.findIndex(i => i.id === item.id)
if (index > -1) {
list.value.splice(index, 1)
uni.showToast({ title: '已删除', icon: 'success' })
}
}
}
})
}
const handleSubmit = () => {
if (!formData.price) {
uni.showToast({ title: '请输入售价', icon: 'none' })
return
}
const newItem = {
id: Math.floor(Math.random() * 1000) + 700,
price: parseFloat(formData.price),
bonus: parseFloat(formData.bonus || '0'),
sort: parseInt(formData.sort.toString()),
is_open: formData.is_open
}
list.value.push(newItem)
// 重置表单
formData.price = ''
formData.bonus = ''
formData.sort = 1
formData.is_open = true
showAddModal.value = false
uni.showToast({ title: '添加成功', icon: 'success' })
}
</script>
<style scoped lang="scss">
.marketing-recharge-quota {
padding: 16px;
background: #f0f2f5;
min-height: 100vh;
}
.content-layout {
display: flex;
flex-direction: row;
}
/* 左侧预览 */
.preview-side {
width: 320px;
margin-right: 24px;
}
.phone-mock {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
padding-bottom: 24px;
}
.phone-header {
height: 120px;
background: #e74c3c;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
}
.balance-label { font-size: 13px; opacity: 0.9; margin-bottom: 8px; }
.balance-val { display: flex; flex-direction: row; align-items: baseline; }
.symbol { font-size: 18px; margin-right: 4px; }
.num { font-size: 32px; font-weight: bold; }
.recharge-tabs {
display: flex;
flex-direction: row;
height: 44px;
border-bottom: 1px solid #f5f5f5;
}
.tab { flex: 1; display: flex; align-items: center; justify-content: center; position: relative; }
.tab.active::after { content: ''; position: absolute; bottom: 0; width: 40px; height: 2px; background: #e74c3c; }
.tab-txt { font-size: 14px; color: #666; font-weight: bold; }
.tab.active .tab-txt { color: #333; }
.quota-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 16px;
justify-content: space-between;
}
.quota-item {
width: 90px;
height: 60px;
border: 1px solid #f0f0f0;
border-radius: 4px;
margin-bottom: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f8f8f8;
}
.quota-item.active {
border-color: #e74c3c;
background: #fff;
position: relative;
}
.quota-item.active::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
border: 1px solid #e74c3c;
border-radius: 4px;
}
.price { font-size: 14px; color: #333; font-weight: bold; margin-bottom: 4px; }
.bonus { font-size: 11px; color: #999; }
.quota-item.active .price { color: #e74c3c; }
.quota-item.active .bonus { color: #e74c3c; opacity: 0.8; }
.other { background: #f0f0f0; border: none; }
.other-txt { font-size: 14px; color: #666; }
.notice-section {
padding: 0 16px;
margin-top: 10px;
}
.notice-title { font-size: 13px; color: #333; font-weight: bold; margin-bottom: 8px; display: block; }
.notice-content { font-size: 12px; color: #999; line-height: 1.6; }
.recharge-btn {
margin: 24px 16px 0;
height: 40px;
line-height: 40px;
background: #e74c3c;
color: #fff;
border-radius: 20px;
font-size: 15px;
border: none;
}
/* 右侧设置 */
.table-side {
flex: 1;
background: #fff;
padding: 24px;
}
.border-shadow { border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
.side-header { border-left: 4px solid #1890ff; padding-left: 12px; margin-bottom: 24px; }
.side-title { font-size: 16px; font-weight: bold; color: #333; }
.action-row { margin-bottom: 16px; }
.btn-primary {
background: #1890ff; color: #fff; border: none;
height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; cursor: pointer;
}
.table-head { display: flex; flex-direction: row; background: #f8f8f9; border-bottom: 1px solid #e8eaec; }
.th { padding: 12px 8px; font-size: 13px; color: #515a6e; font-weight: bold; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; align-items: center; }
.td { padding: 16px 8px; }
.td-txt { font-size: 13px; color: #515a6e; }
.cell-id { width: 80px; }
.cell-price { width: 120px; }
.cell-bonus { width: 120px; }
.cell-status { width: 100px; text-align: center; }
.cell-sort { width: 100px; text-align: center; }
.cell-op { flex: 1; text-align: right; }
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-link.del { color: #ff4d4f; }
.ml-10 { margin-left: 10px; }
.switch-mock {
width: 44px; height: 22px; background-color: #bfbfbf; border-radius: 11px;
display: flex; align-items: center; padding: 0 4px; position: relative;
transition: background-color 0.3s; cursor: pointer;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 14px; height: 14px; background-color: #fff; border-radius: 50%;
position: absolute; left: 4px; transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 26px; }
/* Modal */
.modal-mask {
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 { width: 500px; background: #fff; border-radius: 4px; }
.modal-header { padding: 16px 24px; border-bottom: 1px solid #e8eaec; display: flex; justify-content: space-between; align-items: center; }
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { font-size: 24px; color: #999; cursor: pointer; }
.modal-body { padding: 24px; }
.modal-footer { padding: 12px 24px; border-top: 1px solid #e8eaec; display: flex; justify-content: flex-end; }
.form-item { display: flex; flex-direction: row; margin-bottom: 20px; align-items: center; }
.form-label { width: 80px; font-size: 14px; color: #606266; }
.form-input { flex: 1; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; }
.radio-group { display: flex; flex-direction: row; flex: 1; }
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
.radio-circle { width: 14px; height: 14px; border: 1px solid #dcdfe6; border-radius: 50%; margin-right: 6px; position: relative; }
.radio-circle.checked { border-color: #1890ff; }
.radio-circle.checked::after { content: ''; position: absolute; width: 8px; height: 8px; background: #1890ff; border-radius: 50%; top: 2px; left: 2px; }
.radio-txt { font-size: 14px; color: #606266; }
.ml-20 { margin-left: 20px; }
.btn-cancel { margin-right: 8px; height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; border: 1px solid #dcdfe6; background: #fff; }
.btn-submit { height: 32px; line-height: 32px; padding: 0 16px; font-size: 14px; border-radius: 4px; background: #1890ff; color: #fff; border: none; }
</style>

View File

@@ -1,27 +1,528 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="page">
<view class="header">
<text class="title">{{ title }}</text>
<text class="sub-title">页面占位 (自动生成)</text>
<view class="marketing-seckill-config">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">是否显示:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
</view>
</view>
</AdminLayout>
<view class="action-bar">
<button class="btn-add" @click="showDrawer = true">添加数据</button>
</view>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">编号</view>
<view class="th cell-hour">开启时间(整点)</view>
<view class="th cell-duration">持续时间(整数小时)</view>
<view class="th cell-img">幻灯片</view>
<view class="th cell-sort">排序</view>
<view class="th cell-status">状态</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in configList" :key="item.id" class="table-row">
<view class="td cell-id">
<text class="td-txt">{{ item.id }}</text>
</view>
<view class="td cell-hour">
<text class="td-txt">{{ item.start_hour }}</text>
</view>
<view class="td cell-duration">
<text class="td-txt">{{ item.duration }}</text>
</view>
<view class="td cell-img">
<image v-if="item.image" class="thumb" :src="item.image" mode="aspectFill"></image>
<text v-else class="td-txt">-</text>
</view>
<view class="td cell-sort">
<text class="td-txt">{{ item.sort }}</text>
</view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.status }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.status ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="td cell-op">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">共 {{ configList.length }} 条</text>
</view>
<view class="page-select">
<view class="select-mock mini">
<text class="select-val">20条/页</text>
<text class="arrow">▼</text>
</view>
</view>
</view>
</view>
<!-- Drawer Overlay -->
<view v-if="showDrawer || isAnimating" class="drawer-mask" :class="{ active: showDrawer }" @click="closeDrawer"></view>
<!-- Drawer Panel -->
<view class="drawer-panel" :class="{ active: showDrawer }">
<view class="drawer-header">
<text class="drawer-title">添加数据</text>
<text class="drawer-close" @click="closeDrawer">×</text>
</view>
<view class="drawer-content">
<view class="form-item">
<text class="form-label required">开启时间(整点)</text>
<input class="form-input" placeholder="请输入开启时间(整点)" />
</view>
<view class="form-item">
<text class="form-label required">持续时间(整数小时)</text>
<input class="form-input" placeholder="请输入持续时间(整数小时)" />
</view>
<view class="form-item">
<text class="form-label">幻灯片:</text>
<view class="upload-mock" @click="handleUpload">
<image v-if="formData.image" :src="formData.image" mode="aspectFill" class="thumb" />
<text v-else class="upload-ic">🖼️</text>
</view>
</view>
<view class="form-item">
<text class="form-label">排序:</text>
<input class="form-input" type="number" v-model="formData.sort" placeholder="1" />
</view>
<view class="form-item">
<text class="form-label">状态:</text>
<view class="radio-group">
<view class="radio-item" @click="formData.status = true">
<view class="radio-circle" :class="{ active: formData.status }"></view>
<text class="radio-txt">显示</text>
</view>
<view class="radio-item" @click="formData.status = false">
<view class="radio-circle" :class="{ active: !formData.status }"></view>
<text class="radio-txt">隐藏</text>
</view>
</view>
</view>
</view>
<view class="drawer-footer">
<button class="btn-cancel" @click="closeDrawer">取消</button>
<button class="btn-confirm" @click="handleSubmit">确定</button>
</view>
</view>
</view>
</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')
const showDrawer = ref(false)
const isAnimating = ref(false)
const formData = ref({
id: 0,
start_hour: 0,
duration: 1,
image: '',
sort: 1,
status: true
})
const configList = ref([
{
id: 2268,
start_hour: 6,
duration: 18,
image: '',
sort: 1,
status: true
}
])
const toggleStatus = (item: any) => {
item.status = !item.status
uni.showToast({ title: '修改成功', icon: 'success' })
}
const handleEdit = (item: any) => {
formData.value = { ...item }
showDrawer.value = true
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确定要删除此项配置吗?',
success: (res) => {
if (res.confirm) {
configList.value = configList.value.filter(i => i.id !== item.id)
uni.showToast({ title: '删除成功' })
}
}
})
}
const handleUpload = () => {
uni.chooseImage({
count: 1,
success: (res) => {
formData.value.image = res.tempFilePaths[0]
}
})
}
const handleSubmit = () => {
uni.showToast({ title: '提交成功', icon: 'success' })
closeDrawer()
}
const closeDrawer = () => {
showDrawer.value = false
isAnimating.value = true
setTimeout(() => {
isAnimating.value = false
}, 300)
}
</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; }
.marketing-seckill-config {
min-height: 100vh;
background: #f0f2f5;
padding: 16px;
position: relative;
overflow: hidden;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
/* 过滤栏 */
.filter-card {
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
.select-mock {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
justify-content: space-between;
}
.select-mock.mini { width: 100px; height: 28px; }
.select-val { font-size: 13px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
/* 操作栏 */
.action-bar {
margin-bottom: 16px;
}
.btn-add {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 表格区域 */
.table-card {
padding: 24px;
}
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
/* 各列宽度 */
.cell-id { width: 80px; }
.cell-hour { width: 150px; text-align: center; }
.cell-duration { width: 150px; text-align: center; }
.cell-img { flex: 1; min-width: 150px; }
.cell-sort { width: 100px; text-align: center; }
.cell-status { width: 100px; text-align: center; }
.cell-op { width: 120px; text-align: right; }
.thumb {
width: 40px;
height: 40px;
border-radius: 4px;
}
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-links {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-split { color: #e8eaec; margin: 0 8px; }
/* 分页 */
.pagination-footer {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 13px; color: #606266; }
/* Drawer Styles */
.drawer-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.45);
z-index: 1000;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.drawer-mask.active {
opacity: 1;
pointer-events: auto;
}
.drawer-panel {
position: fixed;
top: 0;
right: -50%;
width: 50%;
height: 100%;
background-color: #fff;
z-index: 1001;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
transition: right 0.3s ease-out;
}
.drawer-panel.active {
right: 0;
}
.drawer-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.drawer-title {
font-size: 16px;
font-weight: 600;
color: #262626;
}
.drawer-close {
font-size: 24px;
color: #bfbfbf;
cursor: pointer;
}
.drawer-content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.form-item {
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 14px;
color: #262626;
margin-bottom: 8px;
}
.required::before {
content: '*';
color: #ff4d4f;
margin-right: 4px;
}
.form-input {
width: 100%;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.upload-mock {
width: 80px;
height: 80px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
cursor: pointer;
}
.upload-ic { font-size: 24px; color: #bfbfbf; }
.radio-group {
display: flex;
flex-direction: row;
gap: 24px;
}
.radio-item {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
cursor: pointer;
}
.radio-circle {
width: 16px;
height: 16px;
border: 1px solid #d9d9d9;
border-radius: 50%;
position: relative;
}
.radio-circle.active {
border-color: #1890ff;
}
.radio-circle.active::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 8px;
height: 8px;
background-color: #1890ff;
border-radius: 50%;
}
.radio-txt { font-size: 14px; color: #262626; }
.drawer-footer {
padding: 10px 16px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: 8px;
}
.btn-cancel, .btn-confirm {
width: auto;
padding: 0 15px;
height: 32px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
}
.btn-cancel {
background-color: #fff;
border: 1px solid #d9d9d9;
color: #595959;
}
.btn-confirm {
background-color: #1890ff;
border: 1px solid #1890ff;
color: #fff;
}
</style>

View File

@@ -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>('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>

View File

@@ -1,15 +1,119 @@
<template>
<view class="page-container">
<view class="page-header">
<text class="page-title">秒杀列表</text>
<text class="page-subtitle">Component: MarketingSeckill</text>
<view class="marketing-seckill-list">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">活动搜索:</text>
<input class="input-mock" placeholder="请输入活动名称, ID" />
</view>
<view class="filter-item">
<text class="label">活动状态:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item">
<text class="label">活动时段:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
</view>
<view class="filter-row mt-16">
<view class="filter-item">
<text class="label">活动时间:</text>
<view class="date-picker-mock">
<text class="calendar-ic">📅</text>
<text class="date-placeholder">开始日期 - 结束日期</text>
</view>
</view>
<button class="btn-query">查询</button>
</view>
</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 class="action-bar">
<button class="btn-add" @click="handleAdd">添加秒杀活动</button>
</view>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-title">活动标题</view>
<view class="th cell-limit">单次限购</view>
<view class="th cell-total">总购买数量限制</view>
<view class="th cell-count">商品数量</view>
<view class="th cell-period">活动时段</view>
<view class="th cell-time">活动时间</view>
<view class="th cell-status">状态</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in seckillList" :key="item.id" class="table-row">
<view class="td cell-id">
<text class="td-txt">{{ item.id }}</text>
</view>
<view class="td cell-title">
<text class="td-txt">{{ item.title }}</text>
</view>
<view class="td cell-limit">
<text class="td-txt">{{ item.single_limit }}</text>
</view>
<view class="td cell-total">
<text class="td-txt">{{ item.total_limit }}</text>
</view>
<view class="td cell-count">
<text class="td-txt">{{ item.product_count }}</text>
</view>
<view class="td cell-period">
<view class="period-tag">
<text class="period-txt">{{ item.time_range }}</text>
</view>
</view>
<view class="td cell-time">
<text class="td-txt-small">开始: {{ item.start_date }}</text>
<text class="td-txt-small">结束: {{ item.end_date }}</text>
</view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.status }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.status ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="td cell-op">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">共 {{ seckillList.length }} 条</text>
</view>
<view class="page-select">
<view class="select-mock mini">
<text class="select-val">15条/页</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="page-btns">
<text class="p-btn disabled"></text>
<text class="p-btn active">1</text>
<text class="p-btn disabled"></text>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<input class="jump-input" placeholder="1" />
<text class="jump-txt">页</text>
</view>
</view>
</view>
</view>
@@ -18,70 +122,258 @@
<script setup lang="uts">
import { ref } from 'vue'
const pageTitle = ref<string>('秒杀列表')
const seckillList = ref([
{
id: 91,
title: '秒杀活动',
single_limit: 1,
total_limit: 10,
product_count: 5,
time_range: '06:00-24:00',
start_date: '2025-07-01 00:00:00',
end_date: '2028-08-22 23:59:59',
status: true
}
])
const toggleStatus = (item: any) => {
item.status = !item.status
uni.showToast({ title: '修改成功', icon: 'success' })
}
const handleAdd = () => {
uni.showToast({ title: '添加活动功能开发中', icon: 'none' })
}
const handleEdit = (item: any) => {
uni.showToast({ title: '编辑活动功能开发中', icon: 'none' })
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确定要删除该活动吗?',
success: (res) => {
if (res.confirm) {
seckillList.value = seckillList.value.filter(i => i.id !== item.id)
uni.showToast({ title: '删除成功' })
}
}
})
}
</script>
<style scoped>
.page-container {
padding: 24px;
background-color: #f0f2f5;
<style scoped lang="scss">
.marketing-seckill-list {
min-height: 100vh;
background: #f0f2f5;
padding: 16px;
}
.page-header {
background-color: #ffffff;
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.mt-16 { margin-top: 16px; }
/* 过滤栏 */
.filter-card {
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;
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
}
.page-subtitle {
display: block;
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #8c8c8c;
color: #606266;
white-space: nowrap;
}
.page-content {
background-color: #ffffff;
.input-mock, .date-picker-mock, .select-mock {
width: 240px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
font-size: 13px;
}
.input-mock { width: 200px; }
.select-mock { width: 160px; justify-content: space-between; }
.select-mock.mini { width: 100px; height: 28px; }
.calendar-ic { font-size: 14px; color: #c0c4cc; margin-right: 8px; }
.date-placeholder { font-size: 13px; color: #c0c4cc; }
.select-val { font-size: 13px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
.btn-query {
width: 64px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 0;
}
/* 操作栏 */
.action-bar {
margin-bottom: 16px;
}
.btn-add {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 表格区域 */
.table-card {
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.placeholder-card {
text-align: center;
padding: 48px 24px;
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.placeholder-title {
display: block;
font-size: 18px;
font-weight: 600;
color: #262626;
margin-bottom: 12px;
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.placeholder-desc {
display: block;
font-size: 14px;
color: #8c8c8c;
margin-bottom: 8px;
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.placeholder-info {
display: block;
font-size: 12px;
color: #bfbfbf;
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
.td-txt-small { font-size: 12px; color: #808695; display: block; }
/* 各列宽度 */
.cell-id { width: 60px; }
.cell-title { flex: 1; min-width: 150px; }
.cell-limit { width: 100px; text-align: center; }
.cell-total { width: 150px; text-align: center; }
.cell-count { width: 100px; text-align: center; }
.cell-period { width: 120px; text-align: center; }
.cell-time { width: 220px; }
.cell-status { width: 100px; text-align: center; }
.cell-op { width: 120px; text-align: right; }
.period-tag {
display: inline-block;
padding: 2px 8px;
border: 1px solid #1890ff;
border-radius: 4px;
background-color: #f0f7ff;
}
.period-txt { color: #1890ff; font-size: 12px; }
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-links {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-split { color: #e8eaec; margin: 0 8px; }
/* 分页 */
.pagination-footer {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 13px; color: #606266; }
.page-btns { display: flex; flex-direction: row; gap: 8px; }
.p-btn {
width: 28px;
height: 28px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
color: #606266;
}
.p-btn.active { background-color: #1890ff; border-color: #1890ff; color: #fff; }
.p-btn.disabled { color: #c0c4cc; cursor: not-allowed; }
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.jump-txt { font-size: 13px; color: #606266; }
.jump-input { width: 40px; height: 28px; border: 1px solid #dcdfe6; border-radius: 4px; text-align: center; }
</style>

View File

@@ -0,0 +1,430 @@
<template>
<view class="marketing-seckill-product">
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item">
<text class="label">商品搜索:</text>
<input class="input-mock" placeholder="请输入商品名称, ID" />
</view>
<view class="filter-item">
<text class="label">活动搜索:</text>
<input class="input-mock" placeholder="请输入活动名称" />
</view>
<view class="filter-item">
<text class="label">活动状态:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
</view>
<view class="filter-row mt-16">
<view class="filter-item">
<text class="label">活动时间:</text>
<view class="date-picker-mock">
<text class="calendar-ic">📅</text>
<text class="date-placeholder">开始日期 - 结束日期</text>
</view>
</view>
<button class="btn-query">查询</button>
</view>
</view>
<view class="action-bar">
<button class="btn-export">导出</button>
</view>
<view class="table-card border-shadow">
<view class="table-container">
<view class="table-head">
<view class="th cell-id">ID</view>
<view class="th cell-img">商品图片</view>
<view class="th cell-title">商品标题</view>
<view class="th cell-intro">商品简介</view>
<view class="th cell-activity">活动名称</view>
<view class="th cell-price">售价</view>
<view class="th cell-price">秒杀价</view>
<view class="th cell-num">限量</view>
<view class="th cell-num">限量剩余</view>
<view class="th cell-status-txt">秒杀状态</view>
<view class="th cell-time">活动时间</view>
<view class="th cell-status">状态</view>
<view class="th cell-op">操作</view>
</view>
<view class="table-body">
<view v-for="item in productList" :key="item.id" class="table-row">
<view class="td cell-id">
<text class="td-txt">{{ item.id }}</text>
</view>
<view class="td cell-img">
<image class="thumb" :src="item.image" mode="aspectFill"></image>
</view>
<view class="td cell-title">
<text class="product-title line-clamp-2">{{ item.title }}</text>
</view>
<view class="td cell-intro">
<text class="product-title line-clamp-2">{{ item.intro }}</text>
</view>
<view class="td cell-activity">
<text class="td-txt">{{ item.activity_name }}</text>
</view>
<view class="td cell-price">
<text class="td-txt">{{ item.price.toFixed(2) }}</text>
</view>
<view class="td cell-price">
<text class="td-txt-price">{{ item.seckill_price.toFixed(2) }}</text>
</view>
<view class="td cell-num">
<text class="td-txt">{{ item.limit }}</text>
</view>
<view class="td cell-num">
<text class="td-txt">{{ item.limit_remaining }}</text>
</view>
<view class="td cell-status-txt">
<text class="status-text green">进行中</text>
</view>
<view class="td cell-time">
<text class="td-txt-small">起: {{ item.start_date }}</text>
<text class="td-txt-small">止: {{ item.end_date }}</text>
</view>
<view class="td cell-status">
<view class="switch-mock" :class="{ active: item.status }" @click="toggleStatus(item)">
<view class="switch-dot"></view>
<text class="switch-txt">{{ item.status ? '开启' : '关闭' }}</text>
</view>
</view>
<view class="td cell-op">
<view class="op-links">
<text class="op-link" @click="handleDelete(item)">删除</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleStats(item)">统计</text>
</view>
</view>
</view>
</view>
</view>
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">共 {{ productList.length }} 条</text>
</view>
<view class="page-select">
<view class="select-mock mini">
<text class="select-val">15条/页</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="page-btns">
<text class="p-btn disabled"></text>
<text class="p-btn active">1</text>
<text class="p-btn disabled"></text>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<input class="jump-input" placeholder="1" />
<text class="jump-txt">页</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const productList = ref([
{
id: 599,
image: 'https://img0.baidu.com/it/u=2257917711,1359654032&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '爱奇艺智能 奇遇LT01 投影仪 家用卧室超...',
intro: '爱奇艺智能 奇遇LT01 投影仪...',
activity_name: '秒杀活动',
price: 44.00,
seckill_price: 1.00,
limit: 100,
limit_remaining: 55,
status: true,
start_date: '2025-07-01 00:00:00',
end_date: '2028-08-22 23:59:59'
},
{
id: 598,
image: 'https://img1.baidu.com/it/u=3175865615,2002599723&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '美式复古阔腿牛仔裤女夏季2024新款高...',
intro: '',
activity_name: '秒杀活动',
price: 49.98,
seckill_price: 20.00,
limit: 750,
limit_remaining: 736,
status: true,
start_date: '2025-07-01 00:00:00',
end_date: '2028-08-22 23:59:59'
},
{
id: 596,
image: 'https://img0.baidu.com/it/u=3023224345,1529124233&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
title: '真力时 (ZENITH) 瑞士手表DEFY系列C...',
intro: '真力时 (ZENITH) 瑞士手表...',
activity_name: '秒杀活动',
price: 61000.00,
seckill_price: 20.00,
limit: 50,
limit_remaining: 46,
status: true,
start_date: '2025-07-01 00:00:00',
end_date: '2028-08-22 23:59:59'
}
])
const toggleStatus = (item: any) => {
item.status = !item.status
uni.showToast({ title: '修改成功', icon: 'success' })
}
const handleDelete = (item: any) => {
uni.showModal({
title: '提示',
content: '确定要删除该秒杀商品吗?',
success: (res) => {
if (res.confirm) {
productList.value = productList.value.filter(i => i.id !== item.id)
uni.showToast({ title: '删除成功' })
}
}
})
}
const handleStats = (item: any) => {
uni.showToast({ title: '统计查看中', icon: 'none' })
}
</script>
<style scoped lang="scss">
.marketing-seckill-product {
min-height: 100vh;
background: #f0f2f5;
padding: 16px;
}
.border-shadow {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.mt-16 { margin-top: 16px; }
/* 过滤栏 */
.filter-card {
padding: 24px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
.input-mock, .date-picker-mock, .select-mock {
width: 220px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
font-size: 13px;
}
.select-mock { width: 160px; justify-content: space-between; }
.select-mock.mini { width: 100px; height: 28px; }
.calendar-ic { font-size: 14px; color: #c0c4cc; margin-right: 8px; }
.date-placeholder { font-size: 13px; color: #c0c4cc; }
.select-val { font-size: 13px; color: #606266; }
.arrow { font-size: 10px; color: #c0c4cc; }
.btn-query {
width: 64px;
height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border: none;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 操作栏 */
.action-bar {
margin-bottom: 16px;
}
.btn-export {
width: auto;
padding: 0 16px;
height: 32px;
background-color: #fff;
color: #606266;
border: 1px solid #dcdfe6;
font-size: 14px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 表格区域 */
.table-card {
padding: 24px;
}
.table-head {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 8px;
font-size: 13px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
align-items: center;
}
.td {
padding: 16px 8px;
}
.td-txt { font-size: 13px; color: #515a6e; }
.td-txt-price { font-size: 13px; color: #ff4d4f; font-weight: bold; }
.td-txt-small { font-size: 12px; color: #808695; display: block; }
/* 各列宽度 */
.cell-id { width: 60px; }
.cell-img { width: 80px; }
.cell-title { width: 200px; }
.cell-intro { width: 180px; }
.cell-activity { width: 120px; }
.cell-price { width: 80px; text-align: center; }
.cell-num { width: 80px; text-align: center; }
.cell-status-txt { width: 100px; text-align: center; }
.cell-time { width: 220px; }
.cell-status { width: 100px; text-align: center; }
.cell-op { width: 120px; text-align: right; }
.thumb {
width: 40px;
height: 40px;
border-radius: 4px;
}
.product-title {
font-size: 13px;
color: #515a6e;
line-height: 1.6;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.status-text { font-size: 12px; }
.status-text.green { color: #52c41a; }
.switch-mock {
width: 50px;
height: 24px;
background-color: #bfbfbf;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 4px;
position: relative;
transition: background-color 0.3s;
}
.switch-mock.active { background-color: #1890ff; }
.switch-dot {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 4px;
transition: left 0.3s;
}
.switch-mock.active .switch-dot { left: 30px; }
.switch-txt { font-size: 11px; color: #fff; margin-left: 20px; }
.switch-mock.active .switch-txt { margin-left: 4px; }
.op-links {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.op-link { color: #1890ff; font-size: 13px; cursor: pointer; }
.op-split { color: #e8eaec; margin: 0 8px; }
/* 分页 */
.pagination-footer {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 13px; color: #606266; }
.page-btns { display: flex; flex-direction: row; gap: 8px; }
.p-btn {
width: 28px;
height: 28px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
color: #606266;
}
.p-btn.active { background-color: #1890ff; border-color: #1890ff; color: #fff; }
.p-btn.disabled { color: #c0c4cc; cursor: not-allowed; }
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.jump-txt { font-size: 13px; color: #606266; }
.jump-input { width: 40px; height: 28px; border: 1px solid #dcdfe6; border-radius: 4px; text-align: center; }
</style>