1100 lines
34 KiB
Vue
1100 lines
34 KiB
Vue
<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'">
|
||
<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>
|
||
|
||
<!-- 客户账号列表弹窗 -->
|
||
<CustomerAccountDialog v-model="customerAccountDialogVisible" :shop-id="currentShopId" />
|
||
|
||
<!-- 店铺操作右键菜单 -->
|
||
<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>当前默认角色</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 {
|
||
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 CustomerAccountDialog from '@/components/business/CustomerAccountDialog.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'
|
||
|
||
defineOptions({ name: 'Shop' })
|
||
|
||
const { hasAuth } = useAuth()
|
||
|
||
const dialogType = ref('add')
|
||
const dialogVisible = ref(false)
|
||
const customerAccountDialogVisible = ref(false)
|
||
const loading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const parentShopLoading = ref(false)
|
||
const currentShopId = ref<number>(0)
|
||
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) => {
|
||
currentShopId.value = row.id
|
||
customerAccountDialogVisible.value = true
|
||
}
|
||
|
||
// 店铺操作菜单项配置
|
||
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>
|