Files
medical-mall/pages/mall/admin/product/protection/index.uvue
2026-02-24 10:35:34 +08:00

422 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="admin-main">
<!-- 头部提示 -->
<view class="alert-info-box">
<text class="alert-info-txt">商品保障可在商品详情页展示,提升用户购买意愿。</text>
</view>
<!-- 数据表格区域 -->
<view class="table-card">
<view class="table-toolbar">
<button class="btn-primary-add" @click="openModal()">添加商品保障</button>
</view>
<view class="table-header-pane">
<view class="th flex-1">ID</view>
<view class="th flex-2">图标</view>
<view class="th flex-4">服务条款名称</view>
<view class="th flex-5">服务描述</view>
<view class="th flex-2 text-center">状态</view>
<view class="th flex-2 text-center">操作</view>
</view>
<view class="table-body">
<view v-if="list.length === 0" class="empty-box">
<text class="empty-text">暂无数据</text>
</view>
<view v-for="(item, index) in list" :key="index" class="table-row-item">
<text class="td flex-1 color-9">{{ item.id }}</text>
<view class="td flex-2">
<image class="protection-icon-img" :src="item.icon" mode="aspectFit"></image>
</view>
<text class="td flex-4">{{ item.name }}</text>
<text class="td flex-5 color-6">{{ item.desc }}</text>
<view class="td flex-2 row-center">
<view class="status-switch-mini" :class="item.status ? 'active' : ''" @click="toggleStatus(index)">
<view class="switch-dot-mini"></view>
</view>
</view>
<view class="td flex-2 row-center">
<text class="btn-action-blue" @click="openModal(item)">编辑</text>
<view class="v-divider-line"></view>
<text class="btn-action-red" @click="deleteItem(index)">删除</text>
</view>
</view>
</view>
</view>
<!-- 添加/编辑弹窗 (居中 Modal) -->
<view class="modal-overlay" v-if="showModal" @click="closeModal">
<view class="modal-main-pane" @click.stop>
<view class="modal-header-box">
<text class="modal-title-txt">添加保障</text>
<text class="modal-close-icon" @click="closeModal">×</text>
</view>
<view class="modal-body-form">
<view class="form-item-box">
<view class="label-box"><text class="form-label font-star">保障名称:</text></view>
<view class="val-box">
<input class="input-ctrl" v-model="form.name" placeholder="请输入保障名称" />
</view>
</view>
<view class="form-item-box row-align-start">
<view class="label-box"><text class="form-label font-star">保障内容:</text></view>
<view class="val-box">
<textarea class="textarea-ctrl" v-model="form.desc" placeholder="请输入保障内容" />
</view>
</view>
<view class="form-item-box">
<view class="label-box"><text class="form-label">图标:</text></view>
<view class="val-box">
<view class="icon-upload-placeholder" @click="mockIconPicker">
<image v-if="form.icon" :src="form.icon" class="icon-preview-img"></image>
<image v-else src="/static/logo.png" class="icon-empty-img" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="form-item-box">
<view class="label-box"><text class="form-label">排序:</text></view>
<view class="val-box">
<input type="number" class="input-ctrl" v-model="form.sort" />
</view>
</view>
<view class="form-item-box">
<view class="label-box"><text class="form-label">是否显示:</text></view>
<view class="val-box row-center-start">
<view class="radio-item" @click="form.status = true">
<view class="radio-circle" :class="form.status ? 'radio-checked' : ''"></view>
<text class="radio-txt">显示</text>
</view>
<view class="radio-item" @click="form.status = false">
<view class="radio-circle" :class="!form.status ? 'radio-checked' : ''"></view>
<text class="radio-txt">隐藏</text>
</view>
</view>
</view>
</view>
<view class="modal-footer-box">
<button class="btn-foot-cancel" @click="closeModal">取消</button>
<button class="btn-foot-submit" @click="saveProtection">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
interface ProtectionItem {
id: number;
name: string;
icon: string;
desc: string;
status: boolean;
sort: number;
}
const list = reactive<ProtectionItem[]>([
{ id: 1, name: '正品保障', icon: '/static/logo.png', desc: '该商品由平台认证,保证百分百正品。', status: true, sort: 0 },
{ id: 2, name: '七天无理由', icon: '/static/logo.png', desc: '商品在不影响二次销售的情况下支持7天无理由退换。', status: true, sort: 0 }
])
const showModal = ref(false)
const isEdit = ref(false)
const editIndex = ref(-1)
const form = reactive({
name: '',
icon: '',
desc: '',
status: true,
sort: 0
})
function openModal(item: ProtectionItem | null = null) {
if (item) {
isEdit.value = true
form.name = item.name
form.icon = item.icon
form.desc = item.desc
form.status = item.status
form.sort = item.sort
editIndex.value = list.indexOf(item)
} else {
isEdit.value = false
form.name = ''
form.icon = ''
form.desc = ''
form.status = true
form.sort = 0
}
showModal.value = true
}
function closeModal() {
showModal.value = false
}
function mockIconPicker() {
uni.showToast({ title: '已模拟选择图标', icon: 'none' })
form.icon = '/static/logo.png'
}
function saveProtection() {
if (!form.name || !form.desc) {
uni.showToast({ title: '请输入必填项', icon: 'none' })
return
}
if (isEdit.value) {
const item = list[editIndex.value]
item.name = form.name
item.icon = form.icon
item.desc = form.desc
item.status = form.status
item.sort = form.sort
} else {
list.unshift({
id: Date.now() % 1000,
name: form.name,
icon: form.icon || '/static/logo.png',
desc: form.desc,
status: form.status,
sort: form.sort
})
}
closeModal()
uni.showToast({ title: '保存成功', icon: 'success' })
}
function toggleStatus(index: number) {
list[index].status = !list[index].status
}
function deleteItem(index: number) {
uni.showModal({
title: '提示',
content: '确定删除该保障条款吗?',
success: (res) => {
if (res.confirm) {
list.splice(index, 1)
}
}
})
}
</script>
<style scoped lang="scss">
.admin-main {
padding: 24px;
background-color: #f0f2f5;
min-height: 100vh;
}
.alert-info-box {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
padding: 10px 16px;
border-radius: 4px;
margin-bottom: 20px;
}
.alert-info-txt { font-size: 14px; color: #1890ff; }
.table-card {
background-color: #fff;
padding: 24px;
border-radius: 4px;
}
.table-toolbar { margin-bottom: 20px; }
.btn-primary-add {
background-color: #1890ff;
color: #fff;
height: 32px;
line-height: 32px;
padding: 0 16px;
font-size: 14px;
border-radius: 4px;
border: none;
margin: 0;
}
.table-header-pane {
display: flex;
flex-direction: row;
background-color: #f8f9fa;
height: 44px;
align-items: center;
}
.th { font-size: 14px; font-weight: bold; padding: 0 12px; }
.table-row-item {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
min-height: 60px;
align-items: center;
}
.td { padding: 0 12px; font-size: 14px; }
.color-9 { color: #999; }
.color-6 { color: #666; }
.protection-icon-img {
width: 30px;
height: 30px;
}
/* Switch */
.status-switch-mini {
width: 44px;
height: 20px;
background-color: #ccc;
border-radius: 10px;
position: relative;
transition: background-color 0.3s;
}
.status-switch-mini.active { background-color: #1890ff; }
.switch-dot-mini {
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
position: absolute;
top: 2px;
left: 2px;
transition: left 0.3s;
}
.status-switch-mini.active .switch-dot-mini { left: 26px; }
.btn-action-blue { color: #1890ff; font-size: 14px; cursor: pointer; }
.btn-action-red { color: #ff4d4f; font-size: 14px; cursor: pointer; }
.v-divider-line { width: 1px; height: 12px; background-color: #eee; margin: 0 10px; }
.row-center { display: flex; flex-direction: row; justify-content: center; align-items: center; }
/* Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
}
.modal-main-pane {
width: 600px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
}
.modal-header-box {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.modal-title-txt { font-size: 16px; font-weight: bold; }
.modal-close-icon { font-size: 18px; color: #999; cursor: pointer; }
.modal-body-form { padding: 24px; }
.form-item-box { display: flex; flex-direction: row; margin-bottom: 20px; align-items: center; }
.row-align-start { align-items: flex-start; }
.label-box { width: 100px; text-align: right; margin-right: 16px; }
.form-label { font-size: 14px; color: #333; }
.font-star::before { content: '*'; color: #ff4d4f; margin-right: 4px; }
.val-box { flex: 1; }
.input-ctrl {
width: 100%;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.textarea-ctrl {
width: 100%;
height: 80px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 8px 12px;
font-size: 14px;
}
.icon-upload-placeholder {
width: 60px;
height: 60px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.icon-empty-img { width: 24px; height: 24px; opacity: 0.3; }
.icon-preview-img { width: 100%; height: 100%; }
.row-center-start { display: flex; flex-direction: row; align-items: center; }
.radio-item { display: flex; flex-direction: row; align-items: center; margin-right: 20px; cursor: pointer; }
.radio-circle {
width: 16px;
height: 16px;
border: 1px solid #dcdfe6;
border-radius: 50%;
margin-right: 6px;
position: relative;
}
.radio-checked { border-color: #1890ff; background-color: #1890ff; }
.radio-checked::after {
content: '';
width: 6px;
height: 6px;
background-color: #fff;
border-radius: 50%;
position: absolute;
top: 4px;
left: 4px;
}
.radio-txt { font-size: 14px; color: #333; }
.modal-footer-box {
padding: 12px 20px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: flex-end;
}
.btn-foot-cancel, .btn-foot-submit {
height: 32px;
line-height: 32px;
padding: 0 20px;
border-radius: 4px;
font-size: 14px;
margin-left: 12px;
}
.btn-foot-cancel { background-color: #fff; border: 1px solid #dcdfe6; color: #606266; }
.btn-foot-submit { background-color: #1890ff; color: #fff; border: none; }
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.flex-4 { flex: 4; }
.flex-5 { flex: 5; }
</style>