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>
|
||||
Reference in New Issue
Block a user