Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
628
src/views/product/shop/index.vue
Normal file
628
src/views/product/shop/index.vue
Normal file
@@ -0,0 +1,628 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="shop-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton @click="showDialog('add')">新增店铺</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
@selection-change="handleSelectionChange"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增店铺' : '编辑店铺'"
|
||||
width="800px"
|
||||
>
|
||||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="店铺名称" prop="shop_name">
|
||||
<ElInput v-model="formData.shop_name" placeholder="请输入店铺名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12" v-if="dialogType === 'add'">
|
||||
<ElFormItem label="店铺编号" prop="shop_code">
|
||||
<ElInput v-model="formData.shop_code" placeholder="请输入店铺编号" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElRow :gutter="20" v-if="dialogType === 'add'">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="上级店铺ID" prop="parent_id">
|
||||
<ElInputNumber v-model="formData.parent_id" :min="1" placeholder="一级店铺可不填" style="width: 100%" clearable />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="省份" prop="province">
|
||||
<ElInput v-model="formData.province" placeholder="请输入省份" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="城市" prop="city">
|
||||
<ElInput v-model="formData.city" placeholder="请输入城市" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="区县" prop="district">
|
||||
<ElInput v-model="formData.district" placeholder="请输入区县" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="详细地址" prop="address">
|
||||
<ElInput v-model="formData.address" placeholder="请输入详细地址" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="联系人" prop="contact_name">
|
||||
<ElInput v-model="formData.contact_name" placeholder="请输入联系人姓名" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="联系电话" prop="contact_phone">
|
||||
<ElInput v-model="formData.contact_phone" placeholder="请输入联系电话" maxlength="11" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 新增店铺时的初始账号信息 -->
|
||||
<template v-if="dialogType === 'add'">
|
||||
<ElDivider content-position="left">初始账号信息</ElDivider>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="用户名" prop="init_username">
|
||||
<ElInput v-model="formData.init_username" placeholder="请输入初始账号用户名" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="密码" prop="init_password">
|
||||
<ElInput v-model="formData.init_password" type="password" placeholder="请输入初始账号密码" show-password />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="手机号" prop="init_phone">
|
||||
<ElInput v-model="formData.init_phone" placeholder="请输入初始账号手机号" maxlength="11" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
|
||||
<ElFormItem v-if="dialogType === 'edit'" label="状态">
|
||||
<ElSwitch
|
||||
v-model="formData.status"
|
||||
:active-value="CommonStatus.ENABLED"
|
||||
:inactive-value="CommonStatus.DISABLED"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit" :loading="submitLoading">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { FormInstance, ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { ShopService } from '@/api/modules'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { ShopResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'Shop' })
|
||||
|
||||
const dialogType = ref('add')
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
shop_name: '',
|
||||
shop_code: '',
|
||||
parent_id: undefined as number | undefined,
|
||||
level: undefined as number | undefined,
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 响应式表单数据
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<ShopResponse[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const tableRef = ref()
|
||||
|
||||
// 选中的行数据
|
||||
const selectedRows = ref<any[]>([])
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.currentPage = 1
|
||||
getShopList()
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
pagination.currentPage = 1
|
||||
getShopList()
|
||||
}
|
||||
|
||||
// 表单配置项
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '店铺名称',
|
||||
prop: 'shop_name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入店铺名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '店铺编号',
|
||||
prop: 'shop_code',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入店铺编号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '上级ID',
|
||||
prop: 'parent_id',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入上级店铺ID'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '店铺层级',
|
||||
prop: 'level',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '1级', value: 1 },
|
||||
{ label: '2级', value: 2 },
|
||||
{ label: '3级', value: 3 },
|
||||
{ label: '4级', value: 4 },
|
||||
{ label: '5级', value: 5 },
|
||||
{ label: '6级', value: 6 },
|
||||
{ label: '7级', value: 7 }
|
||||
],
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择店铺层级'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: STATUS_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'id' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '店铺编号', prop: 'shop_code' },
|
||||
{ label: '层级', prop: 'level' },
|
||||
{ label: '上级ID', prop: 'parent_id' },
|
||||
{ label: '所在地区', prop: 'region' },
|
||||
{ label: '联系人', prop: 'contact_name' },
|
||||
{ label: '联系电话', prop: 'contact_phone' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 显示对话框
|
||||
const showDialog = (type: string, row?: ShopResponse) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
// 重置表单验证状态
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
formData.id = row.id
|
||||
formData.shop_name = row.shop_name
|
||||
formData.shop_code = ''
|
||||
formData.parent_id = null
|
||||
formData.province = row.province || ''
|
||||
formData.city = row.city || ''
|
||||
formData.district = row.district || ''
|
||||
formData.address = row.address || ''
|
||||
formData.contact_name = row.contact_name || ''
|
||||
formData.contact_phone = row.contact_phone || ''
|
||||
formData.status = row.status
|
||||
formData.init_username = ''
|
||||
formData.init_password = ''
|
||||
formData.init_phone = ''
|
||||
} else {
|
||||
formData.id = 0
|
||||
formData.shop_name = ''
|
||||
formData.shop_code = ''
|
||||
formData.parent_id = null
|
||||
formData.province = ''
|
||||
formData.city = ''
|
||||
formData.district = ''
|
||||
formData.address = ''
|
||||
formData.contact_name = ''
|
||||
formData.contact_phone = ''
|
||||
formData.status = CommonStatus.ENABLED
|
||||
formData.init_username = ''
|
||||
formData.init_password = ''
|
||||
formData.init_phone = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 删除店铺
|
||||
const deleteShop = (row: ShopResponse) => {
|
||||
ElMessageBox.confirm(`确定要删除店铺 ${row.shop_name} 吗?`, '删除店铺', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await ShopService.deleteShop(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
getShopList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'shop_code',
|
||||
label: '店铺编号',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'level',
|
||||
label: '层级',
|
||||
width: 80,
|
||||
formatter: (row: ShopResponse) => {
|
||||
return h(ElTag, { type: 'info', size: 'small' }, () => `${row.level}级`)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'parent_id',
|
||||
label: '上级ID',
|
||||
width: 100,
|
||||
formatter: (row: ShopResponse) => row.parent_id || '-'
|
||||
},
|
||||
{
|
||||
prop: 'region',
|
||||
label: '所在地区',
|
||||
minWidth: 180,
|
||||
formatter: (row: ShopResponse) => {
|
||||
const parts: string[] = []
|
||||
if (row.province) parts.push(row.province)
|
||||
if (row.city) parts.push(row.city)
|
||||
if (row.district) parts.push(row.district)
|
||||
return parts.length > 0 ? parts.join(' / ') : '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'contact_name',
|
||||
label: '联系人',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'contact_phone',
|
||||
label: '联系电话',
|
||||
width: 130
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: ShopResponse) => {
|
||||
return h(ElSwitch, {
|
||||
modelValue: row.status,
|
||||
activeValue: CommonStatus.ENABLED,
|
||||
inactiveValue: CommonStatus.DISABLED,
|
||||
activeText: getStatusText(CommonStatus.ENABLED),
|
||||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||||
inlinePrompt: true,
|
||||
'onUpdate:modelValue': (val: number) => handleStatusChange(row, val)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: ShopResponse) => formatDateTime(row.created_at)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
formatter: (row: ShopResponse) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteShop(row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 表单实例
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
shop_name: '',
|
||||
shop_code: '',
|
||||
parent_id: null as number | null,
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
address: '',
|
||||
contact_name: '',
|
||||
contact_phone: '',
|
||||
status: CommonStatus.ENABLED,
|
||||
init_username: '',
|
||||
init_password: '',
|
||||
init_phone: ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getShopList()
|
||||
})
|
||||
|
||||
// 获取店铺列表
|
||||
const getShopList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.currentPage,
|
||||
page_size: pagination.pageSize,
|
||||
shop_name: searchForm.shop_name || undefined,
|
||||
shop_code: searchForm.shop_code || undefined,
|
||||
parent_id: searchForm.parent_id,
|
||||
level: searchForm.level,
|
||||
status: searchForm.status
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取店铺列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
getShopList()
|
||||
}
|
||||
|
||||
// 处理表格行选择变化
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<FormRules>({
|
||||
shop_name: [
|
||||
{ required: true, message: '请输入店铺名称', trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
shop_code: [
|
||||
{ required: true, message: '请输入店铺编号', trigger: 'blur' },
|
||||
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ max: 255, message: '地址不能超过255个字符', trigger: 'blur' }
|
||||
],
|
||||
contact_name: [
|
||||
{ max: 50, message: '联系人姓名不能超过50个字符', trigger: 'blur' }
|
||||
],
|
||||
contact_phone: [
|
||||
{ len: 11, message: '联系电话必须为 11 位', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
|
||||
],
|
||||
init_username: [
|
||||
{ required: true, message: '请输入初始账号用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
init_password: [
|
||||
{ required: true, message: '请输入初始账号密码', trigger: 'blur' },
|
||||
{ min: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
|
||||
],
|
||||
init_phone: [
|
||||
{ required: true, message: '请输入初始账号手机号', trigger: 'blur' },
|
||||
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (dialogType.value === 'add') {
|
||||
const data: any = {
|
||||
shop_name: formData.shop_name,
|
||||
shop_code: formData.shop_code,
|
||||
init_username: formData.init_username,
|
||||
init_password: formData.init_password,
|
||||
init_phone: formData.init_phone
|
||||
}
|
||||
|
||||
// 可选字段
|
||||
if (formData.parent_id) data.parent_id = formData.parent_id
|
||||
if (formData.province) data.province = formData.province
|
||||
if (formData.city) data.city = formData.city
|
||||
if (formData.district) data.district = formData.district
|
||||
if (formData.address) data.address = formData.address
|
||||
if (formData.contact_name) data.contact_name = formData.contact_name
|
||||
if (formData.contact_phone) data.contact_phone = formData.contact_phone
|
||||
|
||||
await ShopService.createShop(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const data: any = {
|
||||
shop_name: formData.shop_name,
|
||||
status: formData.status
|
||||
}
|
||||
|
||||
// 可选字段
|
||||
if (formData.province) data.province = formData.province
|
||||
if (formData.city) data.city = formData.city
|
||||
if (formData.district) data.district = formData.district
|
||||
if (formData.address) data.address = formData.address
|
||||
if (formData.contact_name) data.contact_name = formData.contact_name
|
||||
if (formData.contact_phone) data.contact_phone = formData.contact_phone
|
||||
|
||||
await ShopService.updateShop(formData.id, data)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
getShopList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
getShopList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
getShopList()
|
||||
}
|
||||
|
||||
// 状态切换
|
||||
const handleStatusChange = async (row: ShopResponse, newStatus: number) => {
|
||||
const oldStatus = row.status
|
||||
// 先更新UI
|
||||
row.status = newStatus
|
||||
try {
|
||||
await ShopService.updateShop(row.id, { status: newStatus })
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
// 切换失败,恢复原状态
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shop-page {
|
||||
// 店铺管理页面样式
|
||||
}
|
||||
</style>
|
||||
430
src/views/product/sim-card-assign/index.vue
Normal file
430
src/views/product/sim-card-assign/index.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 搜索和操作区 -->
|
||||
<ElRow :gutter="12">
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="产品名称/ICCID" clearable />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="operatorFilter" placeholder="运营商筛选" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="中国移动" value="CMCC" />
|
||||
<ElOption label="中国联通" value="CUCC" />
|
||||
<ElOption label="中国电信" value="CTCC" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="statusFilter" placeholder="分配状态" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="已分配" value="assigned" />
|
||||
<ElOption label="未分配" value="unassigned" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple type="primary" @click="showAssignDialog">批量分配</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 号卡产品列表 -->
|
||||
<ArtTable :data="filteredData" index style="margin-top: 20px" @selection-change="handleSelectionChange">
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
<ElTableColumn label="产品名称" prop="productName" min-width="180" show-overflow-tooltip />
|
||||
<ElTableColumn label="运营商" prop="operator" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getOperatorTagType(scope.row.operator)">
|
||||
{{ getOperatorText(scope.row.operator) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="套餐规格" prop="packageSpec" min-width="150" />
|
||||
<ElTableColumn label="产品价格" prop="price" width="120">
|
||||
<template #default="scope"> ¥{{ scope.row.price.toFixed(2) }} </template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="库存数量" prop="stock" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<span :style="{ color: scope.row.stock < 100 ? 'var(--el-color-danger)' : '' }">
|
||||
{{ scope.row.stock }}
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="已分配数量" prop="assignedCount" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-primary)">{{ scope.row.assignedCount }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="分配状态" prop="assignStatus" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.assignedCount > 0" type="success">已分配</ElTag>
|
||||
<ElTag v-else type="info">未分配</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button link :icon="View" @click="viewAssignDetail(scope.row)">分配记录</el-button>
|
||||
<el-button link type="primary" @click="assignToAgent(scope.row)">分配</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 分配对话框 -->
|
||||
<ElDialog v-model="assignDialogVisible" title="分配号卡产品" width="600px" align-center>
|
||||
<ElForm ref="formRef" :model="assignForm" :rules="assignRules" label-width="120px">
|
||||
<ElFormItem label="选择代理商" prop="agentId">
|
||||
<ElSelect
|
||||
v-model="assignForm.agentId"
|
||||
placeholder="请选择代理商"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="handleAgentChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="agent in agentList"
|
||||
:key="agent.id"
|
||||
:label="`${agent.agentName} (${agent.phone})`"
|
||||
:value="agent.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="分配数量" prop="quantity">
|
||||
<ElInputNumber
|
||||
v-model="assignForm.quantity"
|
||||
:min="1"
|
||||
:max="currentProduct.stock"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div style="color: var(--el-text-color-secondary); font-size: 12px; margin-top: 4px">
|
||||
当前库存:{{ currentProduct.stock }} 张
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="分佣模式" prop="commissionMode">
|
||||
<ElRadioGroup v-model="assignForm.commissionMode">
|
||||
<ElRadio value="fixed">固定佣金</ElRadio>
|
||||
<ElRadio value="percent">比例佣金</ElRadio>
|
||||
<ElRadio value="template">使用模板</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="assignForm.commissionMode === 'fixed'" label="固定金额" prop="fixedAmount">
|
||||
<ElInputNumber v-model="assignForm.fixedAmount" :min="0" :precision="2" style="width: 100%" />
|
||||
<span style="margin-left: 8px">元/张</span>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="assignForm.commissionMode === 'percent'" label="佣金比例" prop="percent">
|
||||
<ElInputNumber v-model="assignForm.percent" :min="0" :max="100" :precision="2" style="width: 100%" />
|
||||
<span style="margin-left: 8px">%</span>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="assignForm.commissionMode === 'template'" label="分佣模板" prop="templateId">
|
||||
<ElSelect v-model="assignForm.templateId" placeholder="请选择分佣模板" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="template in commissionTemplates"
|
||||
:key="template.id"
|
||||
:label="template.templateName"
|
||||
:value="template.id"
|
||||
>
|
||||
<span>{{ template.templateName }}</span>
|
||||
<span style="float: right; color: var(--el-text-color-secondary); font-size: 12px">
|
||||
{{ template.mode === 'fixed' ? `¥${template.value}元/张` : `${template.value}%` }}
|
||||
</span>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="特殊折扣" prop="discount">
|
||||
<ElInputNumber v-model="assignForm.discount" :min="0" :max="100" :precision="2" style="width: 100%" />
|
||||
<span style="margin-left: 8px">%</span>
|
||||
<div style="color: var(--el-text-color-secondary); font-size: 12px; margin-top: 4px">
|
||||
0表示无折扣,设置后代理商可以此折扣价格销售
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput v-model="assignForm.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="assignDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleAssignSubmit">确认分配</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 分配记录对话框 -->
|
||||
<ElDialog v-model="detailDialogVisible" title="分配记录" width="900px" align-center>
|
||||
<ArtTable :data="assignRecords" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="代理商名称" prop="agentName" min-width="150" />
|
||||
<ElTableColumn label="分配数量" prop="quantity" width="100" align="center" />
|
||||
<ElTableColumn label="分佣模式" prop="commissionMode" width="120">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.commissionMode === 'fixed'" type="warning">固定佣金</ElTag>
|
||||
<ElTag v-else-if="scope.row.commissionMode === 'percent'" type="success">比例佣金</ElTag>
|
||||
<ElTag v-else>模板佣金</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="佣金规则" prop="commissionRule" width="150" />
|
||||
<ElTableColumn label="特殊折扣" prop="discount" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.discount > 0 ? `${scope.row.discount}%` : '无' }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="分配时间" prop="assignTime" width="180" />
|
||||
<ElTableColumn label="操作人" prop="operator" width="100" />
|
||||
<ElTableColumn fixed="right" label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" @click="handleCancelAssign(scope.row)">取消分配</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="detailDialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { View } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'SimCardAssign' })
|
||||
|
||||
interface SimCardProduct {
|
||||
id: string
|
||||
productName: string
|
||||
operator: string
|
||||
packageSpec: string
|
||||
price: number
|
||||
stock: number
|
||||
assignedCount: number
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const operatorFilter = ref('')
|
||||
const statusFilter = ref('')
|
||||
const assignDialogVisible = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const selectedRows = ref<SimCardProduct[]>([])
|
||||
|
||||
const currentProduct = ref<SimCardProduct>({
|
||||
id: '',
|
||||
productName: '',
|
||||
operator: '',
|
||||
packageSpec: '',
|
||||
price: 0,
|
||||
stock: 0,
|
||||
assignedCount: 0
|
||||
})
|
||||
|
||||
const assignForm = reactive({
|
||||
agentId: '',
|
||||
quantity: 1,
|
||||
commissionMode: 'percent',
|
||||
fixedAmount: 0,
|
||||
percent: 10,
|
||||
templateId: '',
|
||||
discount: 0,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const assignRules = reactive<FormRules>({
|
||||
agentId: [{ required: true, message: '请选择代理商', trigger: 'change' }],
|
||||
quantity: [{ required: true, message: '请输入分配数量', trigger: 'blur' }],
|
||||
commissionMode: [{ required: true, message: '请选择分佣模式', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const agentList = ref([
|
||||
{ id: '1', agentName: '华东区总代理', phone: '13800138000', level: 1 },
|
||||
{ id: '2', agentName: '华南区代理', phone: '13900139000', level: 2 },
|
||||
{ id: '3', agentName: '华北区代理', phone: '13700137000', level: 1 }
|
||||
])
|
||||
|
||||
const commissionTemplates = ref([
|
||||
{ id: '1', templateName: '标准代理商佣金', mode: 'percent', value: 10 },
|
||||
{ id: '2', templateName: '特殊套餐固定佣金', mode: 'fixed', value: 50 },
|
||||
{ id: '3', templateName: '高端代理商佣金', mode: 'percent', value: 15 }
|
||||
])
|
||||
|
||||
const mockData = ref<SimCardProduct[]>([
|
||||
{
|
||||
id: '1',
|
||||
productName: '移动4G流量卡-月包100GB',
|
||||
operator: 'CMCC',
|
||||
packageSpec: '100GB/月,有效期1年',
|
||||
price: 80.00,
|
||||
stock: 1000,
|
||||
assignedCount: 500
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
productName: '联通5G流量卡-季包300GB',
|
||||
operator: 'CUCC',
|
||||
packageSpec: '300GB/季,有效期1年',
|
||||
price: 220.00,
|
||||
stock: 500,
|
||||
assignedCount: 200
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
productName: '电信物联网卡-年包1TB',
|
||||
operator: 'CTCC',
|
||||
packageSpec: '1TB/年,有效期2年',
|
||||
price: 800.00,
|
||||
stock: 80,
|
||||
assignedCount: 0
|
||||
}
|
||||
])
|
||||
|
||||
const assignRecords = ref([
|
||||
{
|
||||
id: '1',
|
||||
agentName: '华东区总代理',
|
||||
quantity: 200,
|
||||
commissionMode: 'percent',
|
||||
commissionRule: '10%',
|
||||
discount: 5,
|
||||
assignTime: '2026-01-08 10:00:00',
|
||||
operator: 'admin'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
agentName: '华南区代理',
|
||||
quantity: 150,
|
||||
commissionMode: 'fixed',
|
||||
commissionRule: '¥5.00/张',
|
||||
discount: 0,
|
||||
assignTime: '2026-01-07 14:30:00',
|
||||
operator: 'admin'
|
||||
}
|
||||
])
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
|
||||
if (searchQuery.value) {
|
||||
data = data.filter((item) => item.productName.includes(searchQuery.value))
|
||||
}
|
||||
|
||||
if (operatorFilter.value) {
|
||||
data = data.filter((item) => item.operator === operatorFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
if (statusFilter.value === 'assigned') {
|
||||
data = data.filter((item) => item.assignedCount > 0)
|
||||
} else if (statusFilter.value === 'unassigned') {
|
||||
data = data.filter((item) => item.assignedCount === 0)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
const getOperatorText = (operator: string) => {
|
||||
const map: Record<string, string> = {
|
||||
CMCC: '中国移动',
|
||||
CUCC: '中国联通',
|
||||
CTCC: '中国电信'
|
||||
}
|
||||
return map[operator] || operator
|
||||
}
|
||||
|
||||
const getOperatorTagType = (operator: string) => {
|
||||
const map: Record<string, any> = {
|
||||
CMCC: 'success',
|
||||
CUCC: 'primary',
|
||||
CTCC: 'warning'
|
||||
}
|
||||
return map[operator] || ''
|
||||
}
|
||||
|
||||
const handleSearch = () => {}
|
||||
|
||||
const handleSelectionChange = (rows: SimCardProduct[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
const showAssignDialog = () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.warning('请先选择要分配的产品')
|
||||
return
|
||||
}
|
||||
if (selectedRows.value.length > 1) {
|
||||
ElMessage.warning('批量分配功能开发中,请单个选择')
|
||||
return
|
||||
}
|
||||
currentProduct.value = selectedRows.value[0]
|
||||
assignDialogVisible.value = true
|
||||
}
|
||||
|
||||
const assignToAgent = (row: SimCardProduct) => {
|
||||
currentProduct.value = row
|
||||
assignDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleAgentChange = () => {
|
||||
// 可以根据代理商自动填充默认佣金设置
|
||||
}
|
||||
|
||||
const handleAssignSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
if (assignForm.quantity > currentProduct.value.stock) {
|
||||
ElMessage.error('分配数量不能超过库存数量')
|
||||
return
|
||||
}
|
||||
|
||||
// 更新分配数量
|
||||
currentProduct.value.assignedCount += assignForm.quantity
|
||||
currentProduct.value.stock -= assignForm.quantity
|
||||
|
||||
assignDialogVisible.value = false
|
||||
formRef.value.resetFields()
|
||||
ElMessage.success('分配成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const viewAssignDetail = (row: SimCardProduct) => {
|
||||
currentProduct.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleCancelAssign = (row: any) => {
|
||||
ElMessageBox.confirm('取消分配后,该代理商将无法继续销售此产品,确定取消吗?', '取消分配', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 恢复库存
|
||||
currentProduct.value.stock += row.quantity
|
||||
currentProduct.value.assignedCount -= row.quantity
|
||||
|
||||
const index = assignRecords.value.findIndex((item) => item.id === row.id)
|
||||
if (index !== -1) assignRecords.value.splice(index, 1)
|
||||
ElMessage.success('取消分配成功')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
:deep(.el-select-dropdown__item) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
275
src/views/product/sim-card/index.vue
Normal file
275
src/views/product/sim-card/index.vue
Normal file
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="号卡名称/编码" clearable></ElInput>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="operatorFilter" placeholder="运营商" clearable style="width: 100%">
|
||||
<ElOption label="中国移动" value="cmcc" />
|
||||
<ElOption label="中国联通" value="cucc" />
|
||||
<ElOption label="中国电信" value="ctcc" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple @click="showDialog('add')">新增号卡</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ArtTable :data="filteredData" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="号卡名称" prop="cardName" min-width="150" />
|
||||
<ElTableColumn label="号卡编码" prop="cardCode" />
|
||||
<ElTableColumn label="运营商" prop="operator">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getOperatorTagType(scope.row.operator)">
|
||||
{{ getOperatorText(scope.row.operator) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="套餐类型" prop="packageType" />
|
||||
<ElTableColumn label="月租(元)" prop="monthlyFee">
|
||||
<template #default="scope"> ¥{{ scope.row.monthlyFee.toFixed(2) }} </template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="库存" prop="stock" />
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status === 'online' ? 'success' : 'info'">
|
||||
{{ scope.row.status === 'online' ? '上架' : '下架' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="createTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-button link @click="showDialog('edit', scope.row)">编辑</el-button>
|
||||
<el-button
|
||||
link
|
||||
:type="scope.row.status === 'online' ? 'danger' : 'primary'"
|
||||
@click="toggleStatus(scope.row)"
|
||||
>
|
||||
{{ scope.row.status === 'online' ? '下架' : '上架' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增号卡' : '编辑号卡'"
|
||||
width="700px"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="号卡名称" prop="cardName">
|
||||
<ElInput v-model="form.cardName" placeholder="请输入号卡名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="号卡编码" prop="cardCode">
|
||||
<ElInput v-model="form.cardCode" placeholder="请输入号卡编码" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="运营商" prop="operator">
|
||||
<ElSelect v-model="form.operator" placeholder="请选择运营商" style="width: 100%">
|
||||
<ElOption label="中国移动" value="cmcc" />
|
||||
<ElOption label="中国联通" value="cucc" />
|
||||
<ElOption label="中国电信" value="ctcc" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="套餐类型" prop="packageType">
|
||||
<ElInput v-model="form.packageType" placeholder="例如:流量套餐" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="月租(元)" prop="monthlyFee">
|
||||
<ElInputNumber v-model="form.monthlyFee" :min="0" :precision="2" style="width: 100%" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="初始库存" prop="stock">
|
||||
<ElInputNumber v-model="form.stock" :min="0" style="width: 100%" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElFormItem label="号卡描述" prop="description">
|
||||
<ElInput v-model="form.description" type="textarea" :rows="3" placeholder="请输入号卡描述" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态">
|
||||
<ElSwitch v-model="form.status" active-value="online" inactive-value="offline" />
|
||||
<span style="margin-left: 8px; color: var(--el-text-color-secondary)">{{
|
||||
form.status === 'online' ? '上架' : '下架'
|
||||
}}</span>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'SimCard' })
|
||||
|
||||
interface SimCard {
|
||||
id?: string
|
||||
cardName: string
|
||||
cardCode: string
|
||||
operator: string
|
||||
packageType: string
|
||||
monthlyFee: number
|
||||
stock: number
|
||||
description?: string
|
||||
status: 'online' | 'offline'
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
const mockData = ref<SimCard[]>([
|
||||
{
|
||||
id: '1',
|
||||
cardName: '移动流量卡30GB',
|
||||
cardCode: 'CARD_CMCC_30GB',
|
||||
operator: 'cmcc',
|
||||
packageType: '流量套餐',
|
||||
monthlyFee: 29.9,
|
||||
stock: 1000,
|
||||
description: '移动30GB流量卡,全国通用',
|
||||
status: 'online',
|
||||
createTime: '2026-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
cardName: '联通流量卡50GB',
|
||||
cardCode: 'CARD_CUCC_50GB',
|
||||
operator: 'cucc',
|
||||
packageType: '流量套餐',
|
||||
monthlyFee: 49.9,
|
||||
stock: 800,
|
||||
description: '联通50GB流量卡,全国通用',
|
||||
status: 'online',
|
||||
createTime: '2026-01-02 11:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const operatorFilter = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive<SimCard>({
|
||||
cardName: '',
|
||||
cardCode: '',
|
||||
operator: '',
|
||||
packageType: '',
|
||||
monthlyFee: 0,
|
||||
stock: 0,
|
||||
description: '',
|
||||
status: 'online'
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
cardName: [{ required: true, message: '请输入号卡名称', trigger: 'blur' }],
|
||||
cardCode: [{ required: true, message: '请输入号卡编码', trigger: 'blur' }],
|
||||
operator: [{ required: true, message: '请选择运营商', trigger: 'change' }],
|
||||
monthlyFee: [{ required: true, message: '请输入月租', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
if (searchQuery.value) {
|
||||
data = data.filter(
|
||||
(item) => item.cardName.includes(searchQuery.value) || item.cardCode.includes(searchQuery.value)
|
||||
)
|
||||
}
|
||||
if (operatorFilter.value) {
|
||||
data = data.filter((item) => item.operator === operatorFilter.value)
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
const getOperatorText = (operator: string) => {
|
||||
const map: Record<string, string> = { cmcc: '中国移动', cucc: '中国联通', ctcc: '中国电信' }
|
||||
return map[operator] || '未知'
|
||||
}
|
||||
|
||||
const getOperatorTagType = (operator: string) => {
|
||||
const map: Record<string, string> = { cmcc: '', cucc: 'success', ctcc: 'warning' }
|
||||
return map[operator] || 'info'
|
||||
}
|
||||
|
||||
const handleSearch = () => {}
|
||||
|
||||
const showDialog = (type: 'add' | 'edit', row?: SimCard) => {
|
||||
dialogType.value = type
|
||||
dialogVisible.value = true
|
||||
if (type === 'edit' && row) {
|
||||
Object.assign(form, row)
|
||||
} else {
|
||||
Object.assign(form, {
|
||||
cardName: '',
|
||||
cardCode: '',
|
||||
operator: '',
|
||||
packageType: '',
|
||||
monthlyFee: 0,
|
||||
stock: 0,
|
||||
description: '',
|
||||
status: 'online'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
mockData.value.push({
|
||||
...form,
|
||||
id: Date.now().toString(),
|
||||
createTime: new Date().toLocaleString('zh-CN')
|
||||
})
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const index = mockData.value.findIndex((item) => item.id === form.id)
|
||||
if (index !== -1) mockData.value[index] = { ...form }
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toggleStatus = (row: SimCard) => {
|
||||
const action = row.status === 'online' ? '下架' : '上架'
|
||||
ElMessageBox.confirm(`确定要${action}该号卡吗?`, `${action}确认`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
row.status = row.status === 'online' ? 'offline' : 'online'
|
||||
ElMessage.success(`${action}成功`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user