Files
yudao-ui-admin-vue3/src/views/ydoyun/storeuser/index.vue

512 lines
16 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>
<el-row :gutter="20">
<!-- 左侧部门树 -->
<el-col :span="4" :xs="24">
<ContentWrap class="h-1/1">
<StoreTree ref="storeTreeRef" @node-click="handleStoreNodeClick" />
</ContentWrap>
</el-col>
<el-col :span="20" :xs="24">
<!-- 搜索 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="用户名称" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入用户名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="用户昵称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入用户昵称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="门店名称" prop="storeName">
<el-input
v-model="queryParams.storeName"
placeholder="请输入门店名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="角色" prop="roleId">
<el-select
v-model="queryParams.roleId"
placeholder="请选择角色"
clearable
filterable
class="!w-240px"
>
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange"
start-placeholder="开始日期"
end-placeholder="结束日期"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['ydoyun:store-user:create']"
>
<Icon icon="ep:plus" /> 新增
</el-button>
<el-button
type="info"
plain
@click="handleE3Sync"
:loading="e3SyncLoading"
v-hasPermi="['ydoyun:store-user:e3-sync']"
>
<Icon icon="ep:refresh" />从E3同步账号
</el-button>
<el-button
type="danger"
plain
:disabled="checkedIds.length === 0"
@click="handleDeleteBatch"
v-hasPermi="['ydoyun:store-user:delete']"
>
<Icon icon="ep:delete" />批量删除
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['ydoyun:store-user:export']"
>
<Icon icon="ep:download" />导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list" @selection-change="handleRowCheckboxChange">
<el-table-column type="selection" width="55" />
<el-table-column
label="用户昵称"
align="center"
prop="nickname"
min-width="100"
width="130"
:show-overflow-tooltip="true"
/>
<el-table-column label="用户名称" align="center" min-width="120" width="150">
<template #default="{ row }">
<div class="flex items-center justify-center gap-1 px-1">
<template v-if="row.username?.trim()">
<span class="min-w-0 truncate text-sm">{{ row.username }}</span>
<el-button
link
type="primary"
class="!shrink-0 !p-0"
title="复制"
@click.stop="copyUsername(row.username)"
>
<Icon icon="ep:document-copy" />
</el-button>
</template>
<span v-else class="text-[var(--el-text-color-placeholder)]"></span>
</div>
</template>
</el-table-column>
<el-table-column
label="门店名称"
align="center"
prop="storeName"
min-width="120"
width="160"
:show-overflow-tooltip="true"
/>
<el-table-column label="角色" align="center" min-width="220">
<template #default="{ row }">
<div class="flex flex-wrap items-center justify-center gap-1 px-1">
<span v-if="!row.roleNames?.trim()" class="text-[var(--el-text-color-placeholder)]"
></span
>
<template v-else>
<el-tag
v-for="(name, idx) in splitRoleNames(row.roleNames)"
:key="idx"
size="small"
:type="roleTagType(idx)"
effect="dark"
class="!m-0"
>
{{ name }}
</el-tag>
</template>
</div>
</template>
</el-table-column>
<el-table-column label="手机号码" align="center" prop="mobile" width="120" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="280">
<template #default="scope">
<div class="flex flex-wrap items-center justify-center gap-x-1">
<el-button
type="primary"
link
@click="openForm('update', scope.row.id)"
v-hasPermi="['ydoyun:store-user:update']"
>
<Icon icon="ep:edit" />修改
</el-button>
<el-button
type="primary"
link
@click="handleResetPwd(scope.row)"
v-hasPermi="['ydoyun:store-user:update-password']"
>
<Icon icon="ep:key" />重置密码
</el-button>
<el-dropdown
@command="(command) => handleCommand(command, scope.row)"
v-hasPermi="['ydoyun:store-user:delete', 'system:permission:assign-user-role']"
>
<el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
command="handleDelete"
v-if="checkPermi(['ydoyun:store-user:delete'])"
>
<Icon icon="ep:delete" />删除
</el-dropdown-item>
<el-dropdown-item
command="handleRole"
v-if="checkPermi(['system:permission:assign-user-role'])"
>
<Icon icon="ep:circle-check" />分配角色
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</el-col>
</el-row>
<!-- 添加或修改门店-用户绑定对话框 -->
<StoreUserForm ref="formRef" @success="getList" />
<!-- 分配角色与系统用户一致使用系统用户编号 userId -->
<UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
</template>
<script lang="ts" setup>
import { checkPermi } from '@/utils/permission'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { StoreUser, StoreUserApi } from '@/api/ydoyun/storeuser'
import * as UserApi from '@/api/system/user'
import * as RoleApi from '@/api/system/role'
import StoreTree from '@/views/ydoyun/store/StoreTree.vue'
import StoreUserForm from './StoreUserForm.vue'
import UserAssignRoleForm from '@/views/system/user/UserAssignRoleForm.vue'
defineOptions({ name: 'StoreUser' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
/** 角色名称字符串拆成列表(后端用顿号拼接) */
const splitRoleNames = (roleNames: string | undefined) => {
if (!roleNames?.trim()) {
return []
}
return roleNames
.split(/[、,]/)
.map((s) => s.trim())
.filter(Boolean)
}
/** 角色标签按序使用不同主题色 */
const ROLE_TAG_TYPES = ['primary', 'success', 'warning', 'danger', 'info'] as const
const roleTagType = (index: number) => ROLE_TAG_TYPES[index % ROLE_TAG_TYPES.length]
/** 点击用户名称复制到剪贴板 */
const copyUsername = async (text: string | undefined) => {
const s = text?.trim()
if (!s) {
message.warning('无可复制内容')
return
}
try {
await navigator.clipboard.writeText(s)
message.success('已复制到剪贴板')
} catch {
message.error('复制失败')
}
}
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
storeId: undefined, // 门店ID
userId: undefined, // 用户ID
createTime: [], // 创建时间
username: undefined, // 用户名称
nickname: undefined, // 用户昵称
mobile: undefined, // 手机号码
storeName: undefined as string | undefined, // 门店名称(模糊)
roleId: undefined as number | undefined // 按角色筛选
})
const roleOptions = ref<RoleApi.RoleVO[]>([])
const queryFormRef = ref() // 搜索的表单
const storeTreeRef = ref() // StoreTree组件引用
const currentStoreId = ref<number | undefined>(undefined) // 当前选中的门店ID
const e3SyncLoading = ref(false) // E3同步加载状态
const exportLoading = ref(false)
/** 导出 Excel与当前查询条件一致不含密码 */
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await StoreUserApi.exportStoreUser(queryParams)
download.excel(data, '门店-用户绑定.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StoreUserApi.getStoreUserPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.storeId = undefined
queryParams.storeName = undefined
currentStoreId.value = undefined
storeTreeRef.value?.clearStoreSelection?.()
queryFormRef.value?.resetFields()
handleQuery()
}
/** 处理门店被点击 */
const handleStoreNodeClick = async (row: any) => {
if (row === undefined) {
queryParams.storeId = undefined
currentStoreId.value = undefined
await getList()
} else {
queryParams.storeId = row.id
currentStoreId.value = row.id
await getList()
}
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
if (!currentStoreId.value && type === 'create') {
message.warning('请先选择门店')
return
}
formRef.value.open(type, id, currentStoreId.value)
}
/** E3同步账号 */
const handleE3Sync = async () => {
try {
// 询问用户是否确定同步
await message.confirm('同步时间较长您确定要同步E3门店和账号到本系统吗请耐心等待...')
e3SyncLoading.value = true
// 调用同步接口等待后端响应设置24小时超时确保不会因为超时报错
const result = await StoreUserApi.e3SyncStoreAndUser()
// 检查返回结果是否为true
// 根据axios拦截器如果code === 200返回的是data字段
// 如果后端返回 {code: 200, data: true}那么result应该是true
// 如果后端直接返回true那么result也是true
const isSuccess =
result === true ||
result === 'true' ||
(typeof result === 'object' && result !== null && (result as any).data === true)
if (isSuccess) {
message.success('同步成功')
// 同步完成后刷新StoreTree和StoreUser列表
if (storeTreeRef.value && typeof storeTreeRef.value.getList === 'function') {
await storeTreeRef.value.getList()
}
await getList()
} else {
message.warning('同步完成,但返回结果异常,返回值为:' + JSON.stringify(result))
}
} catch (error: any) {
// 如果是用户取消,不显示错误
if (error !== 'cancel' && error !== 'error') {
console.error('E3同步失败:', error)
// 检查是否是超时错误
if (error?.message && (error.message.includes('timeout') || error.message.includes('超时'))) {
message.error('同步请求超时,请稍后检查同步结果')
} else {
message.error('同步失败:' + (error?.message || '未知错误'))
}
}
} finally {
e3SyncLoading.value = false
}
}
/** 操作分发 */
const handleCommand = (command: string, row: StoreUser) => {
switch (command) {
case 'handleDelete':
handleDelete(row.id!)
break
case 'handleRole':
handleRole(row)
break
default:
break
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await StoreUserApi.deleteStoreUser(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 批量删除按钮操作 */
const checkedIds = ref<number[]>([])
const handleRowCheckboxChange = (rows: StoreUser[]) => {
checkedIds.value = rows.map((row) => row.id!).filter((id): id is number => id !== undefined)
}
const handleDeleteBatch = async () => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起批量删除
await StoreUserApi.deleteStoreUserList(checkedIds.value)
checkedIds.value = []
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 重置密码 */
const handleResetPwd = async (row: StoreUser) => {
try {
if (!row.userId) {
message.warning('用户ID不存在')
return
}
// 重置的二次确认
const result = await message.prompt(
'请输入"' + (row.username || '用户') + '"的新密码',
t('common.reminder')
)
const password = result.value
// 发起重置
await StoreUserApi.resetUserPassword(row.userId, password)
message.success('修改成功,新密码是:' + password)
} catch {}
}
/** 分配角色与系统用户同一套账号open 需传系统用户 id */
const assignRoleFormRef = ref()
const handleRole = (row: StoreUser) => {
if (!row.userId) {
message.warning('用户ID不存在')
return
}
const payload: UserApi.UserVO = {
id: row.userId,
username: row.username,
nickname: row.nickname
} as UserApi.UserVO
assignRoleFormRef.value.open(payload)
}
/** 初始化 */
onMounted(async () => {
roleOptions.value = await RoleApi.getSimpleRoleList()
getList()
})
</script>