Files
one-pipe-system/src/views/product/shop/index.vue
sexygoat b94c043a56
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m45s
fetch(modify):修改bug
2026-02-05 17:22:41 +08:00

1103 lines
34 KiB
Vue
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>
<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')" v-permission="'shop: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"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="false"
:pagination="false"
@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="上级店铺" prop="parent_id">
<ElSelect
v-model="formData.parent_id"
placeholder="一级店铺可不选"
filterable
remote
:remote-method="searchParentShops"
:loading="parentShopLoading"
clearable
style="width: 100%"
>
<ElOption
v-for="shop in parentShopList"
:key="shop.id"
:label="shop.shop_name"
:value="shop.id"
/>
</ElSelect>
</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'">
<div class="form-section-title">
<span class="title-text">初始账号信息</span>
</div>
<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>
<!-- 店铺操作右键菜单 -->
<ArtMenuRight
ref="shopOperationMenuRef"
:menu-items="shopOperationMenuItems"
:menu-width="140"
@select="handleShopOperationMenuSelect"
/>
<!-- 店铺默认角色管理对话框 -->
<ElDialog
v-model="defaultRolesDialogVisible"
:title="`设置默认角色 - ${currentShop?.shop_name || ''}`"
width="800px"
>
<div v-loading="defaultRolesLoading">
<!-- 当前默认角色列表 -->
<div class="default-roles-section">
<div class="section-header">
<span style="color:white;">当前默认角色</span>
<ElButton type="primary" @click="showAddRoleDialog">
添加角色
</ElButton>
</div>
<ElTable :data="currentDefaultRoles" border stripe style="margin-top: 12px">
<ElTableColumn prop="role_name" label="角色名称" width="150" />
<ElTableColumn prop="role_desc" label="角色描述" min-width="200" />
<ElTableColumn prop="status" label="状态" width="80">
<template #default="{ row }">
<ElTag :type="row.status === CommonStatus.ENABLED ? 'success' : 'info'" size="small">
{{ getStatusText(row.status) }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="操作" width="100" fixed="right">
<template #default="{ row }">
<ElButton
type="danger"
text
size="small"
@click="handleDeleteDefaultRole(row)"
>
删除
</ElButton>
</template>
</ElTableColumn>
<template #empty>
<div style="padding: 20px 0; color: #909399;">
暂无默认角色请点击"添加角色"按钮进行配置
</div>
</template>
</ElTable>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="defaultRolesDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
<!-- 添加角色对话框 -->
<ElDialog
v-model="addRoleDialogVisible"
title="添加默认角色"
width="600px"
append-to-body
>
<div v-loading="rolesLoading">
<ElSelect
v-model="selectedRoleIds"
multiple
filterable
placeholder="请选择要添加的角色"
style="width: 100%"
>
<ElOption
v-for="role in availableRoles"
:key="role.role_id"
:label="role.role_name"
:value="role.role_id"
:disabled="isRoleAlreadyAssigned(role.role_id)"
>
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
<div style="display: flex; gap: 8px; align-items: center;">
<span>{{ role.role_name }}</span>
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
</ElTag>
</div>
<span v-if="isRoleAlreadyAssigned(role.role_id)" style="color: #909399; font-size: 12px;">
已添加
</span>
</div>
</ElOption>
</ElSelect>
<div style="margin-top: 8px; color: #909399; font-size: 12px;">
支持多选已添加的角色将显示为禁用状态
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="addRoleDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleAddDefaultRoles" :loading="addRoleLoading">
确定
</ElButton>
</div>
</template>
</ElDialog>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import {
FormInstance,
ElMessage,
ElMessageBox,
ElTag,
ElSwitch,
ElSelect,
ElOption
} from 'element-plus'
import type { FormRules } from 'element-plus'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
import { ShopService, RoleService } from '@/api/modules'
import type { SearchFormItem } from '@/types'
import type { ShopResponse, ShopRoleResponse } from '@/types/api'
import { RoleType } from '@/types/api'
import { formatDateTime } from '@/utils/business/format'
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
import { RoutesAlias } from '@/router/routesAlias'
defineOptions({ name: 'Shop' })
const { hasAuth } = useAuth()
const router = useRouter()
const dialogType = ref('add')
const dialogVisible = ref(false)
const loading = ref(false)
const submitLoading = ref(false)
const parentShopLoading = ref(false)
const parentShopList = ref<ShopResponse[]>([])
const searchParentShopList = ref<ShopResponse[]>([])
// 默认角色管理相关状态
const defaultRolesDialogVisible = ref(false)
const addRoleDialogVisible = ref(false)
const defaultRolesLoading = ref(false)
const rolesLoading = ref(false)
const addRoleLoading = ref(false)
const currentShop = ref<ShopResponse | null>(null)
const currentDefaultRoles = ref<ShopRoleResponse[]>([])
const availableRoles = ref<ShopRoleResponse[]>([])
const selectedRoleIds = ref<number[]>([])
// 右键菜单
const shopOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentOperatingShop = ref<ShopResponse | null>(null)
// 定义表单搜索初始值
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 = computed<SearchFormItem[]>(() => [
{
label: '店铺名称',
prop: 'shop_name',
type: 'input',
config: {
clearable: true,
placeholder: '请输入店铺名称'
}
},
{
label: '店铺编号',
prop: 'shop_code',
type: 'input',
config: {
clearable: true,
placeholder: '请输入店铺编号'
}
},
{
label: '上级店铺',
prop: 'parent_id',
type: 'select',
options: searchParentShopList.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
})),
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleSearchParentShop,
loading: parentShopLoading.value,
placeholder: '请选择或搜索上级店铺'
}
},
{
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: '店铺名称', prop: 'shop_name' },
{ label: '店铺编号', prop: 'shop_code' },
{ label: '层级', prop: 'level' },
{ 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) => {
dialogType.value = type
if (type === 'edit' && row) {
formData.id = row.id
formData.shop_name = row.shop_name
formData.shop_code = ''
formData.parent_id = undefined
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 = undefined
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 = ''
}
// 重置表单验证状态
nextTick(() => {
formRef.value?.clearValidate()
})
dialogVisible.value = true
}
// 删除店铺
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: 'shop_name',
label: '店铺名称',
minWidth: 120
},
{
prop: 'shop_code',
label: '店铺编号',
width: 150,
showOverflowTooltip: true
},
{
prop: 'level',
label: '层级',
width: 80,
formatter: (row: ShopResponse) => {
return h(ElTag, { type: 'info', size: 'small' }, () => `${row.level}`)
}
},
{
prop: 'region',
label: '所在地区',
minWidth: 170,
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)
if (row.address) parts.push(row.address)
return parts.length > 0 ? parts.join(' / ') : '-'
}
},
{
prop: 'contact_name',
label: '联系人',
width: 100
},
{
prop: 'contact_phone',
label: '联系电话',
width: 130
},
{
prop: 'status',
label: '状态',
width: 80,
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: string | number | boolean) =>
handleStatusChange(row, val as number)
})
}
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: ShopResponse) => formatDateTime(row.created_at)
},
{
prop: 'operation',
label: '操作',
width: 200,
fixed: 'right',
formatter: (row: ShopResponse) => {
const buttons = []
if (hasAuth('shop:look_customer')) {
buttons.push(
h(ArtButtonTable, {
text: '账号列表',
onClick: () => viewCustomerAccounts(row)
})
)
}
// 只要有编辑或删除权限之一,就显示更多操作按钮
if (hasAuth('shop:edit') || hasAuth('shop:delete')) {
buttons.push(
h(ArtButtonTable, {
text: '更多操作',
onContextmenu: (e: MouseEvent) => showShopOperationMenu(e, row)
})
)
}
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
}
}
])
// 表单实例
const formRef = ref<FormInstance>()
// 表单数据
const formData = reactive({
id: 0,
shop_name: '',
shop_code: '',
parent_id: undefined as number | undefined,
province: '',
city: '',
district: '',
address: '',
contact_name: '',
contact_phone: '',
status: CommonStatus.ENABLED,
init_username: '',
init_password: '',
init_phone: ''
})
onMounted(() => {
getShopList()
loadParentShopList()
loadSearchParentShopList()
})
// 加载上级店铺列表(用于新增对话框,默认加载20条)
const loadParentShopList = async (shopName?: string) => {
parentShopLoading.value = true
try {
const params: any = {
page: 1,
pageSize: 20
}
if (shopName) {
params.shop_name = shopName
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
parentShopList.value = res.data.items || []
}
} catch (error) {
console.error('获取上级店铺列表失败:', error)
} finally {
parentShopLoading.value = false
}
}
// 加载搜索栏上级店铺列表(默认加载20条)
const loadSearchParentShopList = async (shopName?: string) => {
try {
const params: any = {
page: 1,
pageSize: 20
}
if (shopName) {
params.shop_name = shopName
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
searchParentShopList.value = res.data.items || []
}
} catch (error) {
console.error('获取上级店铺列表失败:', error)
}
}
// 搜索上级店铺(用于新增对话框)
const searchParentShops = (query: string) => {
if (query) {
loadParentShopList(query)
} else {
loadParentShopList()
}
}
// 搜索上级店铺(用于搜索栏)
const handleSearchParentShop = (query: string) => {
if (query) {
loadSearchParentShopList(query)
} else {
loadSearchParentShopList()
}
}
// 获取店铺列表
const getShopList = async () => {
loading.value = true
try {
const params = {
page: 1,
page_size: 9999, // 获取所有数据用于构建树形结构
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) {
const items = res.data.items || []
tableData.value = buildTreeData(items)
pagination.total = res.data.total || 0
}
} catch (error) {
console.error('获取店铺列表失败:', error)
} finally {
loading.value = false
}
}
// 构建树形数据
const buildTreeData = (items: ShopResponse[]) => {
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
const tree: ShopResponse[] = []
// 先将所有项放入 map
items.forEach((item) => {
map.set(item.id, { ...item, children: [] })
})
// 构建树形结构
items.forEach((item) => {
const node = map.get(item.id)!
if (item.parent_id && map.has(item.parent_id)) {
// 有父节点,添加到父节点的 children 中
const parent = map.get(item.parent_id)!
if (!parent.children) parent.children = []
parent.children.push(node)
} else {
// 没有父节点或父节点不存在,作为根节点
tree.push(node)
}
})
return tree
}
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: 2, max: 50, message: '长度在 2 到 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 viewCustomerAccounts = (row: ShopResponse) => {
// 跳转到账号列表页面,通过 query 参数区分店铺类型
router.push({
name: 'EnterpriseCustomerAccounts',
params: { id: row.id },
query: { type: 'shop' }
})
}
// 店铺操作菜单项配置
const shopOperationMenuItems = computed((): MenuItemType[] => {
const items: MenuItemType[] = []
items.push({
key: 'defaultRoles',
label: '默认角色'
})
if (hasAuth('shop:edit')) {
items.push({
key: 'edit',
label: '编辑'
})
}
if (hasAuth('shop:delete')) {
items.push({
key: 'delete',
label: '删除'
})
}
return items
})
// 显示店铺操作右键菜单
const showShopOperationMenu = (e: MouseEvent, row: ShopResponse) => {
e.preventDefault()
e.stopPropagation()
currentOperatingShop.value = row
shopOperationMenuRef.value?.show(e)
}
// 处理店铺操作菜单选择
const handleShopOperationMenuSelect = (item: MenuItemType) => {
if (!currentOperatingShop.value) return
switch (item.key) {
case 'defaultRoles':
showDefaultRolesDialog(currentOperatingShop.value)
break
case 'edit':
showDialog('edit', currentOperatingShop.value)
break
case 'delete':
deleteShop(currentOperatingShop.value)
break
}
}
// 状态切换
const handleStatusChange = async (row: ShopResponse, newStatus: number) => {
const oldStatus = row.status
// 先更新UI
row.status = newStatus
try {
await ShopService.updateShop(row.id, {
shop_name: row.shop_name,
status: newStatus
})
ElMessage.success('状态切换成功')
} catch (error) {
// 切换失败,恢复原状态
row.status = oldStatus
console.error(error)
}
}
// ========== 默认角色管理功能 ==========
// 显示默认角色管理对话框
const showDefaultRolesDialog = async (row: ShopResponse) => {
currentShop.value = row
defaultRolesDialogVisible.value = true
await loadShopDefaultRoles(row.id)
}
// 加载店铺默认角色列表
const loadShopDefaultRoles = async (shopId: number) => {
defaultRolesLoading.value = true
try {
const res = await ShopService.getShopRoles(shopId)
if (res.code === 0) {
currentDefaultRoles.value = res.data.roles || []
}
} catch (error) {
console.error('获取店铺默认角色失败:', error)
ElMessage.error('获取店铺默认角色失败')
} finally {
defaultRolesLoading.value = false
}
}
// 显示添加角色对话框
const showAddRoleDialog = async () => {
addRoleDialogVisible.value = true
selectedRoleIds.value = []
await loadAvailableRoles()
}
// 加载可用角色列表
const loadAvailableRoles = async () => {
rolesLoading.value = true
try {
const res = await RoleService.getRoles({
page: 1,
page_size: 9999,
status: 1 // RoleStatus.ENABLED
})
if (res.code === 0) {
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式,同时保留 role_type
availableRoles.value = (res.data.items || []).map((role) => ({
...role,
role_id: role.ID,
role_name: role.role_name,
role_desc: role.role_desc,
role_type: role.role_type, // 保留角色类型用于显示
shop_id: 0 // 这个值在可用角色列表中不使用
}))
}
} catch (error) {
console.error('获取角色列表失败:', error)
ElMessage.error('获取角色列表失败')
} finally {
rolesLoading.value = false
}
}
// 判断角色是否已分配
const isRoleAlreadyAssigned = (roleId: number) => {
return currentDefaultRoles.value.some((r) => r.role_id === roleId)
}
// 添加默认角色
const handleAddDefaultRoles = async () => {
if (selectedRoleIds.value.length === 0) {
ElMessage.warning('请至少选择一个角色')
return
}
if (!currentShop.value) {
ElMessage.error('店铺信息异常')
return
}
addRoleLoading.value = true
try {
const res = await ShopService.assignShopRoles(currentShop.value.id, {
role_ids: selectedRoleIds.value
})
if (res.code === 0) {
ElMessage.success('添加默认角色成功')
addRoleDialogVisible.value = false
// 刷新默认角色列表
await loadShopDefaultRoles(currentShop.value.id)
}
} catch (error) {
console.error('添加默认角色失败:', error)
} finally {
addRoleLoading.value = false
}
}
// 删除默认角色
const handleDeleteDefaultRole = (role: ShopRoleResponse) => {
ElMessageBox.confirm(`确定要删除默认角色 "${role.role_name}" 吗?`, '删除默认角色', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
if (!currentShop.value) {
ElMessage.error('店铺信息异常')
return
}
try {
await ShopService.deleteShopRole(currentShop.value.id, role.role_id)
ElMessage.success('删除成功')
// 刷新默认角色列表
await loadShopDefaultRoles(currentShop.value.id)
} catch (error) {
console.error('删除默认角色失败:', error)
ElMessage.error('删除默认角色失败')
}
})
.catch(() => {
// 用户取消删除
})
}
</script>
<style lang="scss" scoped>
.shop-page {
// 店铺管理页面样式
}
.default-roles-section {
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #303133;
}
}
</style>