Files
medical-mall/pages/mall/admin/setting/delivery/template.uvue
2026-02-15 16:37:37 +08:00

404 lines
9.9 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-page-container">
<view class="page-card">
<!-- 搜索栏 -->
<view class="search-wrap">
<view class="search-item">
<text class="label">搜索:</text>
<input class="input" placeholder="请输入模板名称" v-model="searchKey" @confirm="onSearch" />
</view>
<button class="btn btn-primary" @click="onSearch">查询</button>
</view>
<view class="action-wrap">
<button class="btn btn-primary" @click="onAdd">添加运费模板</button>
</view>
<!-- 表格区域 -->
<view class="table-wrap">
<!-- Loading 遮罩 -->
<view v-if="isLoading" class="loading-mask">
<text class="loading-text">加载中...</text>
</view>
<view class="table-header">
<view class="th" style="flex: 1;">ID</view>
<view class="th" style="flex: 2;">模板名称</view>
<view class="th" style="flex: 2;">计费方式</view>
<view class="th" style="flex: 2;">是否包邮</view>
<view class="th" style="flex: 1;">排序</view>
<view class="th" style="flex: 2;">操作</view>
</view>
<view class="table-body">
<view v-if="freightList.length === 0 && !isLoading" class="empty-row">
<text>暂无运费模板数据</text>
</view>
<view v-for="item in freightList" :key="item.id" class="tr">
<view class="td" style="flex: 1;"><text class="td-txt-small">{{ item.id }}</text></view>
<view class="td" style="flex: 2;">{{ item.name }}</view>
<view class="td" style="flex: 2;">{{ getCalcMethodName(item.calc_method) }}</view>
<view class="td" style="flex: 2;">{{ item.is_free_shipping ? '是' : '否' }}</view>
<view class="td" style="flex: 1;">{{ item.sort_order }}</view>
<view class="td" style="flex: 2;">
<text class="action-btn" @click="onEdit(item)">编辑</text>
<text class="action-btn-del" @click="onDel(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
<!-- 添加/编辑弹窗 -->
<view v-if="showModal" class="modal-mask" @click="showModal = false">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ isEdit ? '编辑运费模板' : '添加运费模板' }}</text>
<text class="modal-close" @click="showModal = false">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">模板名称:</text>
<input class="form-input" v-model="editForm.name" placeholder="请输入模板名称" />
</view>
<view class="form-item">
<text class="form-label">计费方式:</text>
<picker :range="['按件数', '按重量', '按体积']" @change="(e) => {
const methods = ['piece', 'weight', 'volume']
editForm.calc_method = methods[e.detail.value as number]
}">
<view class="picker-box">{{ getCalcMethodName(editForm.calc_method ?? 'piece') }}</view>
</picker>
</view>
<view class="form-item">
<text class="form-label">是否包邮:</text>
<switch :checked="editForm.is_free_shipping" color="#1890ff" @change="(e) => editForm.is_free_shipping = e.detail.value" />
</view>
<view class="form-item">
<text class="form-label">排序:</text>
<input class="form-input" type="number" v-model="editForm.sort_order" />
</view>
</view>
<view class="modal-footer">
<button class="btn-cancel" @click="showModal = false">取消</button>
<button class="btn-primary" @click="handleSave">保存</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted } from 'vue'
import { fetchShippingTemplates, saveShippingTemplate, deleteShippingTemplate, ShippingTemplate } from '@/services/admin/productService.uts'
const searchKey = ref('')
const isLoading = ref(false)
const freightList = ref<ShippingTemplate[]>([])
const showModal = ref(false)
const isEdit = ref(false)
const editForm = reactive<Partial<ShippingTemplate>>({
id: '',
name: '',
calc_method: 'piece',
is_free_shipping: false,
sort_order: 0
})
onMounted(() => {
loadData()
})
async function loadData() {
isLoading.value = true
try {
const res = await fetchShippingTemplates()
// 简单前端搜索过滤,实际推荐后端过滤
if (searchKey.value.trim() !== '') {
freightList.value = res.filter(item => item.name.includes(searchKey.value.trim()))
} else {
freightList.value = res
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
isLoading.value = false
}
}
function onSearch() {
loadData()
}
function onAdd() {
isEdit.value = false
editForm.id = ''
editForm.name = ''
editForm.calc_method = 'piece'
editForm.is_free_shipping = false
editForm.sort_order = 0
showModal.value = true
}
function onEdit(item: ShippingTemplate) {
isEdit.value = true
editForm.id = item.id
editForm.name = item.name
editForm.calc_method = item.calc_method
editForm.is_free_shipping = item.is_free_shipping
editForm.sort_order = item.sort_order
showModal.value = true
}
async function handleSave() {
if (!editForm.name) {
uni.showToast({ title: '请输入模板名称', icon: 'none' })
return
}
isLoading.value = true
try {
const success = await saveShippingTemplate(editForm)
if (success) {
uni.showToast({ title: '保存成功', icon: 'success' })
showModal.value = false
loadData()
} else {
uni.showToast({ title: '保存失败', icon: 'none' })
}
} finally {
isLoading.value = false
}
}
async function onDel(item: ShippingTemplate) {
uni.showModal({
title: '提示',
content: '确定删除该运费模板吗?',
success: async (res) => {
if (res.confirm) {
isLoading.value = true
try {
const success = await deleteShippingTemplate(item.id)
if (success) {
uni.showToast({ title: '删除成功' })
loadData()
}
} finally {
isLoading.value = false
}
}
}
})
}
function getCalcMethodName(method: string): string {
switch (method) {
case 'piece': return '按件数'
case 'weight': return '按重量'
case 'volume': return '按体积'
default: return method
}
}
</script>
<style scoped>
.admin-page-container {
padding: 15px;
background-color: #f5f7f9;
min-height: 100vh;
}
.page-card {
background-color: #fff;
border-radius: 4px;
padding: 20px;
}
.search-wrap {
display: flex;
flex-direction: row;
align-items: center;
padding-bottom: 20px;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 20px;
}
.label {
font-size: 14px;
color: #606266;
}
.input {
width: 200px;
height: 32px;
padding: 0 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
color: #606266;
margin-right: 15px;
}
.action-wrap {
margin-bottom: 20px;
}
.btn {
height: 32px;
line-height: 30px;
padding: 0 15px;
font-size: 14px;
border-radius: 4px;
border: none;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
}
.table-wrap {
width: 100%;
border: 1px solid #f0f0f0;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
}
.th {
padding: 12px 10px;
font-size: 14px;
font-weight: bold;
color: #515a6e;
border-bottom: 1px solid #f0f0f0;
text-align: center;
}
.tr {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
}
.td {
padding: 12px 10px;
font-size: 14px;
color: #515a6e;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn {
color: #1890ff;
font-size: 13px;
margin-right: 10px;
}
.action-btn-del {
color: #ed4014;
font-size: 13px;
}
/* Loading & Empty Styles */
.table-wrap {
position: relative;
min-height: 300px;
}
.loading-mask {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-text { color: #1890ff; font-size: 14px; }
.empty-row {
padding: 60px 0;
text-align: center;
color: #999;
font-size: 14px;
}
.td-txt-small { font-size: 12px; color: #999; }
/* Modal Styles */
.modal-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
width: 500px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
}
.modal-header {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { font-size: 24px; color: #ccc; cursor: pointer; }
.modal-body { padding: 24px; }
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: 12px;
}
.form-item {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 20px;
}
.form-label { width: 100px; font-size: 14px; color: #666; }
.form-input {
flex: 1;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.picker-box {
flex: 1;
height: 36px;
line-height: 34px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
color: #333;
}
.btn-cancel {
background-color: #fff;
color: #666;
border: 1px solid #dcdfe6;
padding: 0 20px;
height: 32px;
border-radius: 4px;
font-size: 14px;
}
</style>