1. 提交代码
This commit is contained in:
@@ -16,6 +16,8 @@ export interface StoreUser {
|
|||||||
roleNames?: string
|
roleNames?: string
|
||||||
/** 门店名称(列表接口返回) */
|
/** 门店名称(列表接口返回) */
|
||||||
storeName?: string
|
storeName?: string
|
||||||
|
/** 编辑时提交的门店绑定(全量替换) */
|
||||||
|
storeIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 门店-用户绑定 API
|
// 门店-用户绑定 API
|
||||||
@@ -30,6 +32,11 @@ export const StoreUserApi = {
|
|||||||
return await request.get({ url: `/ydoyun/store-user/get?id=` + id })
|
return await request.get({ url: `/ydoyun/store-user/get?id=` + id })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 编辑弹窗:用户账号 + 已绑定门店列表 */
|
||||||
|
getStoreUserForEdit: async (userId: number) => {
|
||||||
|
return await request.get({ url: `/ydoyun/store-user/get-edit`, params: { userId } })
|
||||||
|
},
|
||||||
|
|
||||||
// 新增门店-用户绑定
|
// 新增门店-用户绑定
|
||||||
createStoreUser: async (data: StoreUser) => {
|
createStoreUser: async (data: StoreUser) => {
|
||||||
return await request.post({ url: `/ydoyun/store-user/create`, data })
|
return await request.post({ url: `/ydoyun/store-user/create`, data })
|
||||||
@@ -40,16 +47,36 @@ export const StoreUserApi = {
|
|||||||
return await request.put({ url: `/ydoyun/store-user/update`, data })
|
return await request.put({ url: `/ydoyun/store-user/update`, data })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 删除门店-用户绑定
|
// 删除单条绑定记录(不删账号)
|
||||||
deleteStoreUser: async (id: number) => {
|
deleteStoreUser: async (id: number) => {
|
||||||
return await request.delete({ url: `/ydoyun/store-user/delete?id=` + id })
|
return await request.delete({ url: `/ydoyun/store-user/delete?id=` + id })
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 批量删除门店-用户绑定 */
|
/** 按用户 + 门店解除绑定(不删账号) */
|
||||||
|
deleteStoreUserBinding: async (userId: number, storeId: number) => {
|
||||||
|
return await request.delete({
|
||||||
|
url: `/ydoyun/store-user/delete-binding`,
|
||||||
|
params: { userId, storeId }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 删除用户账号及全部门店绑定 */
|
||||||
|
deleteStoreUserAccount: async (userId: number) => {
|
||||||
|
return await request.delete({ url: `/ydoyun/store-user/delete-user`, params: { userId } })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除绑定记录(不删账号) */
|
||||||
deleteStoreUserList: async (ids: number[]) => {
|
deleteStoreUserList: async (ids: number[]) => {
|
||||||
return await request.delete({ url: `/ydoyun/store-user/delete-list?ids=${ids.join(',')}` })
|
return await request.delete({ url: `/ydoyun/store-user/delete-list?ids=${ids.join(',')}` })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 批量删除用户账号 */
|
||||||
|
deleteStoreUserAccountList: async (userIds: number[]) => {
|
||||||
|
return await request.delete({
|
||||||
|
url: `/ydoyun/store-user/delete-user-list?userIds=${userIds.join(',')}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 导出门店-用户绑定 Excel
|
// 导出门店-用户绑定 Excel
|
||||||
exportStoreUser: async (params: any) => {
|
exportStoreUser: async (params: any) => {
|
||||||
return await request.download({ url: `/ydoyun/store-user/export-excel`, params })
|
return await request.download({ url: `/ydoyun/store-user/export-excel`, params })
|
||||||
|
|||||||
@@ -420,7 +420,8 @@ export default {
|
|||||||
sex: 'Sex',
|
sex: 'Sex',
|
||||||
man: 'Man',
|
man: 'Man',
|
||||||
woman: 'Woman',
|
woman: 'Woman',
|
||||||
createTime: 'Created Date'
|
createTime: 'Created Date',
|
||||||
|
avatar: 'Avatar'
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
title: 'Basic Information',
|
title: 'Basic Information',
|
||||||
|
|||||||
@@ -414,7 +414,8 @@ export default {
|
|||||||
sex: '性别',
|
sex: '性别',
|
||||||
man: '男',
|
man: '男',
|
||||||
woman: '女',
|
woman: '女',
|
||||||
createTime: '创建日期'
|
createTime: '创建日期',
|
||||||
|
avatar: '头像'
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
title: '基本信息',
|
title: '基本信息',
|
||||||
|
|||||||
@@ -830,6 +830,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
canTo: true
|
canTo: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/ydoyun/report/lijun/reportpage6/detail.vue')
|
component: () => import('@/views/ydoyun/report/lijun/reportpage6/detail.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'product-splb',
|
||||||
|
name: 'ProductSplbReport',
|
||||||
|
meta: {
|
||||||
|
title: '商品报表',
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/ydoyun/report/productSplb/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="basic-info-avatar-row">
|
||||||
|
<span class="basic-info-avatar-label">{{ t('profile.user.avatar') }}</span>
|
||||||
|
<UserAvatar :img="profileAvatar" />
|
||||||
|
</div>
|
||||||
<Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
|
<Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
|
||||||
<template #sex="form">
|
<template #sex="form">
|
||||||
<el-radio-group v-model="form['sex']">
|
<el-radio-group v-model="form['sex']">
|
||||||
@@ -22,6 +26,7 @@ import {
|
|||||||
UserProfileUpdateReqVO
|
UserProfileUpdateReqVO
|
||||||
} from '@/api/system/user/profile'
|
} from '@/api/system/user/profile'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import UserAvatar from './UserAvatar.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'BasicInfo' })
|
defineOptions({ name: 'BasicInfo' })
|
||||||
|
|
||||||
@@ -78,19 +83,20 @@ const schema = reactive<FormSchema[]>([
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
const formRef = ref<FormExpose>() // 表单 Ref
|
const formRef = ref<FormExpose>() // 表单 Ref
|
||||||
|
/** 基本设置内头像展示,与表单、store 同步 */
|
||||||
|
const profileAvatar = ref('')
|
||||||
|
|
||||||
// 监听 userStore 中头像的变化,同步更新表单数据
|
// 监听 userStore 中头像的变化,同步更新表单与头像区
|
||||||
watch(
|
watch(
|
||||||
() => userStore.getUser.avatar,
|
() => userStore.getUser.avatar,
|
||||||
(newAvatar) => {
|
(newAvatar) => {
|
||||||
if (newAvatar && formRef.value) {
|
if (!newAvatar) return
|
||||||
// 直接更新表单模型中的头像字段
|
profileAvatar.value = newAvatar
|
||||||
const formModel = formRef.value.formModel
|
const formModel = formRef.value?.formModel
|
||||||
if (formModel) {
|
if (formModel) {
|
||||||
formModel.avatar = newAvatar
|
formModel.avatar = newAvatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
@@ -112,6 +118,7 @@ const submit = () => {
|
|||||||
const init = async () => {
|
const init = async () => {
|
||||||
const res = await getUserProfile()
|
const res = await getUserProfile()
|
||||||
unref(formRef)?.setValues(res)
|
unref(formRef)?.setValues(res)
|
||||||
|
profileAvatar.value = res.avatar || ''
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,3 +126,21 @@ onMounted(async () => {
|
|||||||
await init()
|
await init()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.basic-info-avatar-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic-info-avatar-label {
|
||||||
|
width: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-right: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
text-align: right;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1394,8 +1394,13 @@ onMounted(async () => {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
.product-image {
|
.product-image {
|
||||||
|
box-sizing: border-box;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
|
min-width: 48px;
|
||||||
|
min-height: 56px;
|
||||||
|
max-width: 48px;
|
||||||
|
max-height: 56px;
|
||||||
background-color: var(--el-fill-color);
|
background-color: var(--el-fill-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1406,9 +1411,14 @@ onMounted(async () => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.product-img {
|
.product-img {
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1537,8 +1547,13 @@ onMounted(async () => {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
.kb22-thumb-box {
|
.kb22-thumb-box {
|
||||||
|
box-sizing: border-box;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 96px;
|
height: 96px;
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 96px;
|
||||||
|
max-width: 80px;
|
||||||
|
max-height: 96px;
|
||||||
background: var(--el-fill-color);
|
background: var(--el-fill-color);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1548,9 +1563,14 @@ onMounted(async () => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.kb22-thumb-img {
|
.kb22-thumb-img {
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kb22-no-img {
|
.kb22-no-img {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -149,9 +149,8 @@
|
|||||||
<div class="time-elapsed-line" :style="{ left: clampPercent(monthTimeProgress) + '%' }"></div>
|
<div class="time-elapsed-line" :style="{ left: clampPercent(monthTimeProgress) + '%' }"></div>
|
||||||
<div class="target-marker" :style="{ left: '100%' }"></div>
|
<div class="target-marker" :style="{ left: '100%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bullet-time-under" aria-label="时间进度">
|
<div class="bullet-time-under" :aria-label="monthVsTimeProgressDisplay.ariaLabel">
|
||||||
<span class="bullet-time-label">时间进度</span>
|
<span class="bullet-time-label">时间进度 {{ formatPercent(monthTimeProgress) }},<span :class="monthVsTimeProgressDisplay.cls">{{ monthVsTimeProgressDisplay.text }}</span></span>
|
||||||
<span class="bullet-time-value">{{ formatPercent(monthTimeProgress) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -898,6 +897,36 @@ const todayGoalRmb = computed(() => {
|
|||||||
const monthGoalRate = computed(() => normalizePercent(summary.bydcl))
|
const monthGoalRate = computed(() => normalizePercent(summary.bydcl))
|
||||||
const monthTimeProgress = computed(() => normalizePercent(summary.bysjjd))
|
const monthTimeProgress = computed(() => normalizePercent(summary.bysjjd))
|
||||||
|
|
||||||
|
/** 本月累计完成率、时间进度按「百分点」取值;无效时为 NaN(与 normalizePercent 规则一致) */
|
||||||
|
function toPercentPoints(v: any): number {
|
||||||
|
const n = toFiniteNumber(v)
|
||||||
|
if (!Number.isFinite(n)) return NaN
|
||||||
|
return n <= 1 ? n * 100 : n
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对时间进度的领先/落后:本月累计完成率 − 时间进度(百分点)。
|
||||||
|
* 为正表示完成进度快于日历时间(领先),为负表示落后于时间进度。
|
||||||
|
*/
|
||||||
|
const monthVsTimeProgressDisplay = computed(() => {
|
||||||
|
const rate = toPercentPoints(summary.bydcl)
|
||||||
|
const time = toPercentPoints(summary.bysjjd)
|
||||||
|
if (!Number.isFinite(rate) || !Number.isFinite(time)) {
|
||||||
|
return { text: '--', cls: '', ariaLabel: '相对时间进度:数据不足' } as const
|
||||||
|
}
|
||||||
|
const gap = rate - time
|
||||||
|
if (Math.abs(gap) < 0.05) {
|
||||||
|
const t = '与时间进度持平'
|
||||||
|
return { text: t, cls: '', ariaLabel: `相对时间进度:${t}` } as const
|
||||||
|
}
|
||||||
|
if (gap > 0) {
|
||||||
|
const t = `时间进度领先 ${gap.toFixed(1)}%`
|
||||||
|
return { text: t, cls: 'sxrb-metric-up', ariaLabel: `相对时间进度:${t}` } as const
|
||||||
|
}
|
||||||
|
const t = `时间进度落后 ${Math.abs(gap).toFixed(1)}%`
|
||||||
|
return { text: t, cls: 'sxrb-metric-down', ariaLabel: `相对时间进度:${t}` } as const
|
||||||
|
})
|
||||||
|
|
||||||
function clampPercent(v: any): number {
|
function clampPercent(v: any): number {
|
||||||
const n = normalizePercent(v)
|
const n = normalizePercent(v)
|
||||||
return Math.min(100, Math.max(0, n))
|
return Math.min(100, Math.max(0, n))
|
||||||
|
|||||||
296
src/views/ydoyun/report/wangbuliao/商品列表.html
Normal file
296
src/views/ydoyun/report/wangbuliao/商品列表.html
Normal file
File diff suppressed because one or more lines are too long
@@ -14,7 +14,25 @@
|
|||||||
<el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
|
<el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="手机号码" prop="mobile">
|
<el-form-item label="手机号码" prop="mobile">
|
||||||
<el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" />
|
<el-input v-model="formData.mobile" placeholder="选填" maxlength="11" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="formType === 'update'" label="门店绑定" prop="storeIds">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.storeIds"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
placeholder="请选择门店(可多家)"
|
||||||
|
class="!w-full"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="opt in storeOptions"
|
||||||
|
:key="opt.id"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="formType === 'create'" label="密码" prop="password">
|
<el-form-item v-if="formType === 'create'" label="密码" prop="password">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -36,94 +54,135 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StoreUserApi, StoreUser } from '@/api/ydoyun/storeuser'
|
import { StoreUserApi, StoreUser } from '@/api/ydoyun/storeuser'
|
||||||
|
import { StoreApi } from '@/api/ydoyun/store'
|
||||||
|
|
||||||
/** 门店-用户绑定 表单 */
|
/** 门店用户表单(新增单门店绑定 / 编辑账号与多门店绑定) */
|
||||||
defineOptions({ name: 'StoreUserForm' })
|
defineOptions({ name: 'StoreUserForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n()
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage()
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formLoading = ref(false)
|
||||||
|
const formType = ref('')
|
||||||
|
const storeOptions = ref<{ id: number; label: string }[]>([])
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
id: undefined,
|
id: undefined as number | undefined,
|
||||||
storeId: undefined,
|
storeId: undefined as number | undefined,
|
||||||
userId: undefined,
|
userId: undefined as number | undefined,
|
||||||
username: undefined,
|
username: undefined as string | undefined,
|
||||||
nickname: undefined,
|
nickname: undefined as string | undefined,
|
||||||
password: undefined,
|
password: undefined as string | undefined,
|
||||||
mobile: undefined
|
mobile: undefined as string | undefined,
|
||||||
|
storeIds: [] as number[]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mobilePattern =
|
||||||
|
/^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/
|
||||||
|
|
||||||
const formRules = computed(() => {
|
const formRules = computed(() => {
|
||||||
const rules: any = {
|
const rules: any = {
|
||||||
username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
|
username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
|
||||||
nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
|
nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
|
||||||
mobile: [
|
mobile: [
|
||||||
{
|
{
|
||||||
pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/,
|
validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
|
||||||
message: '请输入正确的手机号码',
|
const s = value?.trim()
|
||||||
|
if (!s) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!mobilePattern.test(s)) {
|
||||||
|
callback(new Error('请输入正确的手机号码'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
// 只有新增时才需要密码
|
|
||||||
if (formType.value === 'create') {
|
if (formType.value === 'create') {
|
||||||
rules.password = [{ required: true, message: '密码不能为空', trigger: 'blur' }]
|
rules.password = [{ required: true, message: '密码不能为空', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
return rules
|
return rules
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
|
||||||
|
|
||||||
/** 打开弹窗 */
|
const formRef = ref()
|
||||||
const open = async (type: string, id?: number, storeId?: number) => {
|
|
||||||
|
const loadStoreOptions = async () => {
|
||||||
|
const data = await StoreApi.getStorePage({ pageNo: 1, pageSize: -1 })
|
||||||
|
const list = data?.list ?? []
|
||||||
|
storeOptions.value = list.map((s: { id: number; storeCode?: string; storeName?: string }) => {
|
||||||
|
const code = s.storeCode?.trim()
|
||||||
|
const name = s.storeName?.trim()
|
||||||
|
const label = [code, name].filter(Boolean).join(' ') || `门店#${s.id}`
|
||||||
|
return { id: s.id, label }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开弹窗:update 时 id 为系统用户 userId */
|
||||||
|
const open = async (type: string, userId?: number, treeStoreId?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
dialogTitle.value = t('action.' + type)
|
dialogTitle.value = t('action.' + type)
|
||||||
formType.value = type
|
formType.value = type
|
||||||
resetForm()
|
resetForm()
|
||||||
// 设置门店ID
|
await loadStoreOptions()
|
||||||
if (storeId) {
|
if (treeStoreId) {
|
||||||
formData.value.storeId = storeId
|
formData.value.storeId = treeStoreId
|
||||||
}
|
}
|
||||||
// 修改时,设置数据
|
if (type === 'update' && userId) {
|
||||||
if (id) {
|
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = await StoreUserApi.getStoreUser(id)
|
const data = await StoreUserApi.getStoreUserForEdit(userId)
|
||||||
formData.value = { ...data, storeId: storeId || data.storeId }
|
formData.value.userId = data.userId
|
||||||
|
formData.value.username = data.username
|
||||||
|
formData.value.nickname = data.nickname
|
||||||
|
formData.value.mobile = data.mobile ?? ''
|
||||||
|
formData.value.storeIds = Array.isArray(data.storeIds) ? [...data.storeIds] : []
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
||||||
|
|
||||||
/** 提交表单 */
|
defineExpose({ open })
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
// 校验表单
|
|
||||||
await formRef.value.validate()
|
await formRef.value.validate()
|
||||||
// 提交请求
|
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = formData.value as unknown as StoreUser
|
|
||||||
if (formType.value === 'create') {
|
if (formType.value === 'create') {
|
||||||
|
const data: StoreUser = {
|
||||||
|
storeId: formData.value.storeId,
|
||||||
|
username: formData.value.username,
|
||||||
|
nickname: formData.value.nickname,
|
||||||
|
mobile: formData.value.mobile,
|
||||||
|
password: formData.value.password
|
||||||
|
}
|
||||||
await StoreUserApi.createStoreUser(data)
|
await StoreUserApi.createStoreUser(data)
|
||||||
message.success(t('common.createSuccess'))
|
message.success(t('common.createSuccess'))
|
||||||
} else {
|
} else {
|
||||||
await StoreUserApi.updateStoreUser(data)
|
await StoreUserApi.updateStoreUser({
|
||||||
|
userId: formData.value.userId,
|
||||||
|
username: formData.value.username,
|
||||||
|
nickname: formData.value.nickname,
|
||||||
|
mobile: formData.value.mobile,
|
||||||
|
storeIds: formData.value.storeIds ?? []
|
||||||
|
})
|
||||||
message.success(t('common.updateSuccess'))
|
message.success(t('common.updateSuccess'))
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
// 发送操作成功的事件
|
|
||||||
emit('success')
|
emit('success')
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -132,7 +191,8 @@ const resetForm = () => {
|
|||||||
username: undefined,
|
username: undefined,
|
||||||
nickname: undefined,
|
nickname: undefined,
|
||||||
password: undefined,
|
password: undefined,
|
||||||
mobile: undefined
|
mobile: undefined,
|
||||||
|
storeIds: []
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,13 +99,22 @@
|
|||||||
<Icon icon="ep:refresh" />从E3同步账号
|
<Icon icon="ep:refresh" />从E3同步账号
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="warning"
|
||||||
plain
|
plain
|
||||||
:disabled="checkedIds.length === 0"
|
:disabled="checkedUserIds.length === 0 || !currentStoreId"
|
||||||
@click="handleDeleteBatch"
|
@click="handleDeleteBatchBinding"
|
||||||
v-hasPermi="['ydoyun:store-user:delete']"
|
v-hasPermi="['ydoyun:store-user:delete']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:delete" />批量删除
|
<Icon icon="ep:link" />批量解除绑定
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="checkedUserIds.length === 0"
|
||||||
|
@click="handleDeleteBatchUsers"
|
||||||
|
v-hasPermi="['ydoyun:store-user:delete']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" />批量删除用户
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
@@ -120,7 +129,12 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" @selection-change="handleRowCheckboxChange">
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
row-key="userId"
|
||||||
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="用户昵称"
|
label="用户昵称"
|
||||||
@@ -186,13 +200,13 @@
|
|||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
width="180"
|
width="180"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center" width="280">
|
<el-table-column label="操作" align="center" width="320">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="flex flex-wrap items-center justify-center gap-x-1">
|
<div class="flex flex-wrap items-center justify-center gap-x-1">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
@click="openForm('update', scope.row.id)"
|
@click="openForm('update', scope.row.userId)"
|
||||||
v-hasPermi="['ydoyun:store-user:update']"
|
v-hasPermi="['ydoyun:store-user:update']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:edit" />修改
|
<Icon icon="ep:edit" />修改
|
||||||
@@ -213,10 +227,16 @@
|
|||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
command="handleDelete"
|
command="handleDeleteBinding"
|
||||||
v-if="checkPermi(['ydoyun:store-user:delete'])"
|
v-if="checkPermi(['ydoyun:store-user:delete'])"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:delete" />删除
|
<Icon icon="ep:remove" />解除门店绑定
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
command="handleDeleteUser"
|
||||||
|
v-if="checkPermi(['ydoyun:store-user:delete'])"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" />删除用户
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
command="handleRole"
|
command="handleRole"
|
||||||
@@ -370,12 +390,12 @@ const handleStoreNodeClick = async (row: any) => {
|
|||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, userId?: number) => {
|
||||||
if (!currentStoreId.value && type === 'create') {
|
if (!currentStoreId.value && type === 'create') {
|
||||||
message.warning('请先选择门店')
|
message.warning('请先选择门店')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
formRef.value.open(type, id, currentStoreId.value)
|
formRef.value.open(type, userId, currentStoreId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** E3同步账号 */
|
/** E3同步账号 */
|
||||||
@@ -426,8 +446,11 @@ const handleE3Sync = async () => {
|
|||||||
/** 操作分发 */
|
/** 操作分发 */
|
||||||
const handleCommand = (command: string, row: StoreUser) => {
|
const handleCommand = (command: string, row: StoreUser) => {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'handleDelete':
|
case 'handleDeleteBinding':
|
||||||
handleDelete(row.id!)
|
handleDeleteBinding(row)
|
||||||
|
break
|
||||||
|
case 'handleDeleteUser':
|
||||||
|
handleDeleteUser(row)
|
||||||
break
|
break
|
||||||
case 'handleRole':
|
case 'handleRole':
|
||||||
handleRole(row)
|
handleRole(row)
|
||||||
@@ -437,34 +460,84 @@ const handleCommand = (command: string, row: StoreUser) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 解除当前选中门店下的绑定(不删账号) */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDeleteBinding = async (row: StoreUser) => {
|
||||||
|
if (!currentStoreId.value) {
|
||||||
|
message.warning('请先在左侧选择门店,再解除该用户与此门店的绑定')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!row.userId) {
|
||||||
|
message.warning('用户ID不存在')
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
await message.confirm(
|
||||||
await message.delConfirm()
|
`确定解除用户「${row.nickname || row.username || row.userId}」与当前选中门店的绑定吗?不会删除系统账号。`
|
||||||
// 发起删除
|
)
|
||||||
await StoreUserApi.deleteStoreUser(id)
|
await StoreUserApi.deleteStoreUserBinding(row.userId, currentStoreId.value)
|
||||||
message.success(t('common.delSuccess'))
|
message.success('已解除绑定')
|
||||||
// 刷新列表
|
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除按钮操作 */
|
/** 删除用户账号及全部门店绑定 */
|
||||||
const checkedIds = ref<number[]>([])
|
const handleDeleteUser = async (row: StoreUser) => {
|
||||||
const handleRowCheckboxChange = (rows: StoreUser[]) => {
|
if (!row.userId) {
|
||||||
checkedIds.value = rows.map((row) => row.id!).filter((id): id is number => id !== undefined)
|
message.warning('用户ID不存在')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await message.confirm(
|
||||||
|
`确定删除用户「${row.nickname || row.username || row.userId}」及其全部门店绑定吗?此操作将删除系统账号及权限数据,不可恢复。`
|
||||||
|
)
|
||||||
|
await StoreUserApi.deleteStoreUserAccount(row.userId)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteBatch = async () => {
|
/** 勾选行为系统用户编号 */
|
||||||
|
const checkedUserIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (rows: StoreUser[]) => {
|
||||||
|
checkedUserIds.value = rows
|
||||||
|
.map((row) => row.userId!)
|
||||||
|
.filter((id): id is number => id !== undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量解除当前门店绑定 */
|
||||||
|
const handleDeleteBatchBinding = async () => {
|
||||||
|
if (!currentStoreId.value) {
|
||||||
|
message.warning('请先在左侧选择门店')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (checkedUserIds.value.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
await message.confirm(
|
||||||
await message.delConfirm()
|
`确定解除已选 ${checkedUserIds.value.length} 个用户与当前门店的绑定吗?不会删除系统账号。`
|
||||||
// 发起批量删除
|
)
|
||||||
await StoreUserApi.deleteStoreUserList(checkedIds.value)
|
for (const uid of checkedUserIds.value) {
|
||||||
checkedIds.value = []
|
await StoreUserApi.deleteStoreUserBinding(uid, currentStoreId.value)
|
||||||
|
}
|
||||||
|
checkedUserIds.value = []
|
||||||
|
message.success('已批量解除绑定')
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除用户 */
|
||||||
|
const handleDeleteBatchUsers = async () => {
|
||||||
|
if (checkedUserIds.value.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await message.confirm(
|
||||||
|
`确定删除已选 ${checkedUserIds.value.length} 个用户及其全部门店绑定吗?将删除系统账号及权限数据,不可恢复。`
|
||||||
|
)
|
||||||
|
await StoreUserApi.deleteStoreUserAccountList(checkedUserIds.value)
|
||||||
|
checkedUserIds.value = []
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user