Files
medical-mall/pages/mall/admin/product/specifications/index.uvue

509 lines
12 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="search-card">
<view class="search-row">
<view class="search-item">
<text class="search-label">规格搜索:</text>
<input class="search-input" placeholder="请输入规格名称" />
</view>
<button class="btn-query">查询</button>
</view>
</view>
<!-- 数据表格区域 -->
<view class="table-card">
<view class="table-toolbar">
<button class="btn-add" @click="showModal = true">添加商品规格</button>
<button class="btn-batch-del">批量删除</button>
</view>
<view class="table-header">
<view class="th-cell flex-1 row-center">
<view class="checkbox-mock"></view>
</view>
<text class="th-cell flex-1">ID</text>
<text class="th-cell flex-3">规格名称</text>
<text class="th-cell flex-4">商品规格</text>
<text class="th-cell flex-4">商品属性</text>
<text class="th-cell flex-2 text-center">操作</text>
</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 pagedList" :key="index" class="table-row">
<view class="td-cell flex-1 row-center">
<view class="checkbox-mock" :class="item.selected ? 'checked' : ''" @click="item.selected = !item.selected">
<text v-if="item.selected" class="check-mark">✓</text>
</view>
</view>
<text class="td-cell flex-1 color-9">{{ item.id }}</text>
<text class="td-cell flex-3">{{ item.name }}</text>
<text class="td-cell flex-4">{{ item.specs }}</text>
<text class="td-cell flex-4">{{ item.attrs }}</text>
<view class="td-cell flex-2 row-center">
<text class="btn-link">编辑</text>
<view class="divider"></view>
<text class="btn-link delete" @click="deleteItem(index)">删除</text>
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
<!-- 添加规格弹窗 -->
<view class="modal-mask" v-if="showModal" @click="showModal = false">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">添加商品规格</text>
<text class="modal-close" @click="showModal = false">×</text>
</view>
<view class="modal-body">
<view class="modal-form">
<view class="form-item">
<view class="form-label-box"><text class="form-label">规格名称:</text></view>
<view class="form-input-box">
<input class="modal-input" v-model="form.name" placeholder="请输入规格名称" />
</view>
</view>
<view class="form-item">
<view class="form-label-box"><text class="form-label">商品规格:</text></view>
<view class="form-input-box">
<input class="modal-input" v-model="form.specs" placeholder="请输入商品规格" />
</view>
</view>
<view class="form-item">
<view class="form-label-box"><text class="form-label">商品属性:</text></view>
<view class="form-input-box">
<input class="modal-input" v-model="form.attrs" placeholder="请输入商品属性" />
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn-modal-cancel" @click="showModal = false">取消</button>
<button class="btn-modal-submit" @click="saveAttr">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
interface AttrItem {
id: number;
name: string;
specs: string;
attrs: string;
selected: boolean;
}
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 list 为 fetchSpecList() 调用
const list = reactive<AttrItem[]>([
{ id: 104, name: '颜色', specs: '红色,蓝色,黑色,白色', attrs: '颜色属性', selected: false },
{ id: 105, name: '尺寸', specs: 'S,M,L,XL,XXL', attrs: '服装尺寸', selected: false },
{ id: 106, name: '材质', specs: '纯棉,濯纶,真丝', attrs: '面料材质', selected: false },
{ id: 107, name: '内存', specs: '8G,16G,32G', attrs: '硬件参数', selected: false },
{ id: 108, name: '存储', specs: '128G,256G,512G', attrs: '容量', selected: false },
{ id: 109, name: '重量', specs: '100g,200g,500g,1kg', attrs: '包装规格', selected: false },
{ id: 110, name: '口味', specs: '原味,辣味,甜味,咋味', attrs: '食品口味', selected: false },
{ id: 111, name: '风格', specs: '日系,韩系,欧美,新中式', attrs: '服装风格', selected: false },
{ id: 112, name: '屏幕尺寸', specs: '6.1小时,6.7小时,6.9小时', attrs: '手机屏幕', selected: false },
{ id: 113, name: '套餐选择', specs: '套餐A,套餐B,套餐C', attrs: '餐飲套餐', selected: false },
{ id: 114, name: '独立包装', specs: '独立包装,组合装', attrs: '包装方式', selected: false },
{ id: 115, name: '靘刀', specs: '靘刀7天,靘刀14天,靘刀30天', attrs: '服装保洁', selected: false },
{ id: 116, name: '宣传图', specs: '带宣传图,不带宣传图', attrs: '商品配送', selected: false },
{ id: 117, name: '主题色', specs: '科技蓝,记忆红,森林绿,太空黑', attrs: '设备配色', selected: false },
{ id: 118, name: '等级', specs: '普通版,标准版,旗舰版', attrs: '商品等级', selected: false }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(10)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => list.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return list.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
const showModal = ref(false)
const form = reactive({
name: '',
specs: '',
attrs: ''
})
function saveAttr() {
if (!form.name) {
uni.showToast({ title: '请输入规格名称', icon: 'none' })
return
}
list.push({
id: Math.floor(Math.random() * 1000),
name: form.name,
specs: form.specs,
attrs: form.attrs,
selected: false
})
showModal.value = false
form.name = ''
form.specs = ''
form.attrs = ''
uni.showToast({ title: '添加成功', icon: 'success' })
}
function deleteItem(index: number) {
uni.showModal({
title: '提示',
content: '确定删除该规格吗?',
success: (res) => {
if (res.confirm) {
list.splice(index, 1)
}
}
})
}
</script>
<style scoped lang="scss">
.admin-main {
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
/* 搜索卡片 */
.search-card {
background-color: #fff;
padding: var(--admin-card-padding);
border-radius: 4px;
margin-bottom: var(--admin-section-gap);
}
.search-row {
display: flex;
flex-direction: row;
align-items: center;
}
.search-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 20px;
}
.search-label {
font-size: 14px;
color: #333;
margin-right: 12px;
}
.search-input {
width: 250px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 13px;
}
.btn-query {
width: 64px;
height: 32px;
line-height: 32px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border-radius: 4px;
border: none;
margin-left: 0;
}
/* 表格区域 */
.table-card {
background-color: #fff;
padding: var(--admin-card-padding);
border-radius: 4px;
}
.table-toolbar {
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.btn-add {
height: 32px;
line-height: 32px;
padding: 0 15px;
background-color: #1890ff;
color: #fff;
font-size: 14px;
border-radius: 4px;
margin-left: 0;
margin-right: 12px;
border: none;
}
.btn-batch-del {
height: 32px;
line-height: 32px;
padding: 0 15px;
background-color: #fff;
color: #606266;
border: 1px solid #dcdfe6;
font-size: 14px;
border-radius: 4px;
margin-left: 0;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #f8f9fa;
border-bottom: 1px solid #f0f0f0;
height: 48px;
align-items: center;
}
.th-cell {
padding: 0 16px;
font-size: 14px;
font-weight: bold;
color: #333;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
min-height: 54px;
align-items: center;
}
.empty-box {
padding: 60px 0;
text-align: center;
}
.empty-text {
color: #999;
font-size: 14px;
}
.td-cell {
padding: 0 16px;
}
.color-9 {
color: #999;
}
.checkbox-mock {
width: 16px;
height: 16px;
border: 1px solid #dcdfe6;
border-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.checkbox-mock.checked {
background-color: #1890ff;
border-color: #1890ff;
}
.check-mark {
color: #fff;
font-size: 12px;
}
.btn-link {
font-size: 14px;
color: #1890ff;
cursor: pointer;
}
.btn-link.delete {
color: #ff4d4f;
}
.divider {
width: 1px;
height: 14px;
background-color: #f0f0f0;
margin: 0 12px;
}
.text-center {
text-align: center;
}
.row-center {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
/* Modal styles */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
width: 500px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.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: 500;
}
.modal-close {
font-size: 24px;
color: #999;
cursor: pointer;
}
.modal-body {
padding: 24px;
}
.form-item {
display: flex;
flex-direction: row;
margin-bottom: 20px;
align-items: center;
}
.form-label-box {
width: 80px;
text-align: right;
margin-right: 16px;
}
.form-label {
font-size: 14px;
color: #606266;
}
.form-input-box {
flex: 1;
}
.modal-input {
width: 100%;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.modal-footer {
padding: 12px 24px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.btn-modal-cancel, .btn-modal-submit {
height: 32px;
line-height: 32px;
padding: 0 20px;
font-size: 14px;
border-radius: 4px;
margin-left: 12px;
}
.btn-modal-cancel {
background-color: #fff;
border: 1px solid #dcdfe6;
color: #606266;
}
.btn-modal-submit {
background-color: #1890ff;
color: #fff;
border: none;
}
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.flex-3 { flex: 3; }
.flex-4 { flex: 4; }
</style>