333 lines
11 KiB
Vue
333 lines
11 KiB
Vue
|
|
<template>
|
|||
|
|
<el-dialog
|
|||
|
|
:title="!dataForm.id ? '添加用户' : '修改'"
|
|||
|
|
:close-on-click-modal="false"
|
|||
|
|
v-model="visible">
|
|||
|
|
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="dataFormSubmit()" label-width="100px">
|
|||
|
|
<template v-if="!dataForm.id">
|
|||
|
|
<el-form-item label="归属方式" prop="bindScopeType">
|
|||
|
|
<el-radio-group v-model="dataForm.bindScopeType" @change="onBindScopeChange">
|
|||
|
|
<el-radio value="channel_agent">渠道 / 代理商</el-radio>
|
|||
|
|
<el-radio value="store">门店</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item
|
|||
|
|
v-if="dataForm.bindScopeType === 'channel_agent'"
|
|||
|
|
label="所属组织"
|
|||
|
|
prop="selectedOrgId">
|
|||
|
|
<org-tree-select
|
|||
|
|
v-model="dataForm.selectedOrgId"
|
|||
|
|
:hide-node-types="orgTreeHideTypes"
|
|||
|
|
height="320px"
|
|||
|
|
@change="onOrgTreeChange" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item v-else label="所属门店" prop="storeOrgId">
|
|||
|
|
<store-select
|
|||
|
|
v-model="dataForm.storeOrgId"
|
|||
|
|
width="100%"
|
|||
|
|
@change="onStoreSelectChange" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</template>
|
|||
|
|
<el-form-item label="用户名" prop="userName">
|
|||
|
|
<el-input v-model="dataForm.userName" placeholder="登录帐号"></el-input>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="密码" prop="password" :class="{ 'is-required': !dataForm.id }">
|
|||
|
|
<el-input
|
|||
|
|
v-model="dataForm.password"
|
|||
|
|
type="password"
|
|||
|
|
:placeholder="dataForm.id ? '不改请留空' : '密码'"
|
|||
|
|
autocomplete="new-password"></el-input>
|
|||
|
|
<div v-if="!dataForm.id" class="form-tip" style="margin-top: 4px;">最短8位,且须同时包含小写、大写、数字、特殊字符4种</div>
|
|||
|
|
<div v-else class="form-tip" style="margin-top: 4px;">不修改密码请留空</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="确认密码" prop="comfirmPassword" :class="{ 'is-required': !dataForm.id }">
|
|||
|
|
<el-input
|
|||
|
|
v-model="dataForm.comfirmPassword"
|
|||
|
|
type="password"
|
|||
|
|
:placeholder="dataForm.id ? '不改请留空' : '确认密码'"
|
|||
|
|
autocomplete="new-password"></el-input>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="邮箱" prop="email">
|
|||
|
|
<el-input v-model="dataForm.email" placeholder="选填"></el-input>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="手机号" prop="mobile">
|
|||
|
|
<el-input v-model="dataForm.mobile" placeholder="选填"></el-input>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
<template #footer>
|
|||
|
|
<span class="dialog-footer">
|
|||
|
|
<el-button @click="visible = false">取消</el-button>
|
|||
|
|
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
|||
|
|
</span>
|
|||
|
|
</template>
|
|||
|
|
</el-dialog>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { reactive, ref, nextTick } from 'vue'
|
|||
|
|
import { getUserInfo, saveUser, type UserInfo } from '@/api/sys'
|
|||
|
|
import { isEmail, isMobile } from '@/utils/validate'
|
|||
|
|
import { ElMessage, type FormInstance, type FormItemRule } from 'element-plus'
|
|||
|
|
import OrgTreeSelect from '@/components/org-tree-select/index.vue'
|
|||
|
|
import StoreSelect from '@/components/store-select/index.vue'
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
refreshDataList: []
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
interface OrgTreePayload {
|
|||
|
|
orgId: string | number | null
|
|||
|
|
channelOrgId?: string | number | null
|
|||
|
|
agentOrgId?: string | number | null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 组织树仅展示渠道/代理商,不包含门店与平台根 */
|
|||
|
|
const orgTreeHideTypes = ['PLATFORM', 'STORE']
|
|||
|
|
|
|||
|
|
const visible = ref<boolean>(false)
|
|||
|
|
const dataFormRef = ref<FormInstance | null>(null)
|
|||
|
|
|
|||
|
|
const orgBindings = reactive<OrgTreePayload>({
|
|||
|
|
orgId: null,
|
|||
|
|
channelOrgId: null,
|
|||
|
|
agentOrgId: null
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
interface DataForm {
|
|||
|
|
id: number
|
|||
|
|
bindScopeType: 'channel_agent' | 'store'
|
|||
|
|
selectedOrgId: string | number | undefined
|
|||
|
|
storeOrgId: string | number | undefined
|
|||
|
|
userName: string
|
|||
|
|
password: string
|
|||
|
|
comfirmPassword: string
|
|||
|
|
salt: string
|
|||
|
|
email: string
|
|||
|
|
mobile: string
|
|||
|
|
status: number
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dataForm = reactive<DataForm>({
|
|||
|
|
id: 0,
|
|||
|
|
bindScopeType: 'channel_agent',
|
|||
|
|
selectedOrgId: undefined,
|
|||
|
|
storeOrgId: undefined,
|
|||
|
|
userName: '',
|
|||
|
|
password: '',
|
|||
|
|
comfirmPassword: '',
|
|||
|
|
salt: '',
|
|||
|
|
email: '',
|
|||
|
|
mobile: '',
|
|||
|
|
status: 1
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
function onOrgTreeChange (payload: OrgTreePayload): void {
|
|||
|
|
orgBindings.orgId = payload.orgId
|
|||
|
|
orgBindings.channelOrgId = payload.channelOrgId
|
|||
|
|
orgBindings.agentOrgId = payload.agentOrgId
|
|||
|
|
nextTick(() => {
|
|||
|
|
dataFormRef.value?.validateField('selectedOrgId')
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onBindScopeChange (): void {
|
|||
|
|
dataForm.selectedOrgId = undefined
|
|||
|
|
dataForm.storeOrgId = undefined
|
|||
|
|
orgBindings.orgId = null
|
|||
|
|
orgBindings.channelOrgId = null
|
|||
|
|
orgBindings.agentOrgId = null
|
|||
|
|
nextTick(() => {
|
|||
|
|
dataFormRef.value?.clearValidate(['selectedOrgId', 'storeOrgId'])
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onStoreSelectChange (): void {
|
|||
|
|
nextTick(() => {
|
|||
|
|
dataFormRef.value?.validateField('storeOrgId')
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function validatePasswordStrength (password: string): { valid: boolean; message?: string } {
|
|||
|
|
if (!password || password.length < 8) return { valid: false, message: '密码长度不能少于8位' }
|
|||
|
|
const hasLower = /[a-z]/.test(password)
|
|||
|
|
const hasUpper = /[A-Z]/.test(password)
|
|||
|
|
const hasDigit = /[0-9]/.test(password)
|
|||
|
|
const hasSpecial = /[^A-Za-z0-9]/.test(password)
|
|||
|
|
if (!hasLower) return { valid: false, message: '密码须包含小写字母' }
|
|||
|
|
if (!hasUpper) return { valid: false, message: '密码须包含大写字母' }
|
|||
|
|
if (!hasDigit) return { valid: false, message: '密码须包含数字' }
|
|||
|
|
if (!hasSpecial) return { valid: false, message: '密码须包含特殊字符' }
|
|||
|
|
return { valid: true }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validatePassword: FormItemRule['validator'] = (_rule, value, callback) => {
|
|||
|
|
const v = typeof value === 'string' ? value.trim() : ''
|
|||
|
|
if (!dataForm.id && !v) {
|
|||
|
|
callback(new Error('密码不能为空'))
|
|||
|
|
} else if (!dataForm.id && v) {
|
|||
|
|
const r = validatePasswordStrength(v)
|
|||
|
|
if (!r.valid) return callback(new Error(r.message))
|
|||
|
|
callback()
|
|||
|
|
} else if (dataForm.id && v) {
|
|||
|
|
const r = validatePasswordStrength(v)
|
|||
|
|
if (!r.valid) return callback(new Error(r.message))
|
|||
|
|
callback()
|
|||
|
|
} else {
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validateComfirmPassword: FormItemRule['validator'] = (_rule, value, callback) => {
|
|||
|
|
const confirm = typeof value === 'string' ? value.trim() : ''
|
|||
|
|
const pwd = typeof dataForm.password === 'string' ? dataForm.password.trim() : ''
|
|||
|
|
if (!dataForm.id && !confirm) {
|
|||
|
|
callback(new Error('确认密码不能为空'))
|
|||
|
|
} else if (!pwd && confirm) {
|
|||
|
|
callback(new Error('请先填写密码'))
|
|||
|
|
} else if (pwd !== confirm) {
|
|||
|
|
callback(new Error('确认密码与密码输入不一致'))
|
|||
|
|
} else {
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validateEmail: FormItemRule['validator'] = (_rule, value, callback) => {
|
|||
|
|
if (!value || !String(value).trim()) {
|
|||
|
|
return callback()
|
|||
|
|
}
|
|||
|
|
if (!isEmail(value as string)) {
|
|||
|
|
callback(new Error('邮箱格式错误'))
|
|||
|
|
} else {
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validateMobile: FormItemRule['validator'] = (_rule, value, callback) => {
|
|||
|
|
if (!value || !String(value).trim()) {
|
|||
|
|
return callback()
|
|||
|
|
}
|
|||
|
|
if (!isMobile(value as string)) {
|
|||
|
|
callback(new Error('手机号格式错误'))
|
|||
|
|
} else {
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validateChannelAgentOrg: FormItemRule['validator'] = (_rule, value, callback) => {
|
|||
|
|
if (dataForm.id || dataForm.bindScopeType !== 'channel_agent') return callback()
|
|||
|
|
if (value == null || value === '') {
|
|||
|
|
return callback(new Error('请选择渠道或代理商'))
|
|||
|
|
}
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validateStoreOrg: FormItemRule['validator'] = (_rule, value, callback) => {
|
|||
|
|
if (dataForm.id || dataForm.bindScopeType !== 'store') return callback()
|
|||
|
|
if (value == null || value === '') {
|
|||
|
|
return callback(new Error('请选择门店'))
|
|||
|
|
}
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dataRule: Record<string, FormItemRule[]> = {
|
|||
|
|
selectedOrgId: [{ validator: validateChannelAgentOrg, trigger: 'change' }],
|
|||
|
|
storeOrgId: [{ validator: validateStoreOrg, trigger: 'change' }],
|
|||
|
|
userName: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
|
|||
|
|
password: [{ validator: validatePassword, trigger: 'blur' }],
|
|||
|
|
comfirmPassword: [{ validator: validateComfirmPassword, trigger: 'blur' }],
|
|||
|
|
email: [{ validator: validateEmail, trigger: 'blur' }],
|
|||
|
|
mobile: [{ validator: validateMobile, trigger: 'blur' }]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function init (id?: number | string | null): void {
|
|||
|
|
dataForm.id = id ? Number(id) : 0
|
|||
|
|
visible.value = true
|
|||
|
|
|
|||
|
|
nextTick(() => {
|
|||
|
|
if (dataFormRef.value && dataFormRef.value.clearValidate) {
|
|||
|
|
dataFormRef.value.clearValidate()
|
|||
|
|
}
|
|||
|
|
dataForm.bindScopeType = 'channel_agent'
|
|||
|
|
dataForm.selectedOrgId = undefined
|
|||
|
|
dataForm.storeOrgId = undefined
|
|||
|
|
orgBindings.orgId = null
|
|||
|
|
orgBindings.channelOrgId = null
|
|||
|
|
orgBindings.agentOrgId = null
|
|||
|
|
dataForm.userName = ''
|
|||
|
|
dataForm.password = ''
|
|||
|
|
dataForm.comfirmPassword = ''
|
|||
|
|
dataForm.salt = ''
|
|||
|
|
dataForm.email = ''
|
|||
|
|
dataForm.mobile = ''
|
|||
|
|
dataForm.status = 1
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (id !== null && id !== undefined && id !== '') {
|
|||
|
|
getUserInfo(Number(id)).then((body) => {
|
|||
|
|
if (!body || body.code !== 0) return
|
|||
|
|
const env = body as unknown as { data?: UserInfo; user?: UserInfo }
|
|||
|
|
const user = env.data ?? env.user
|
|||
|
|
if (!user || typeof user !== 'object') return
|
|||
|
|
dataForm.id = user.id
|
|||
|
|
dataForm.userName = user.username || ''
|
|||
|
|
dataForm.salt = user.salt || ''
|
|||
|
|
dataForm.email = user.email || ''
|
|||
|
|
dataForm.mobile = user.mobile || ''
|
|||
|
|
dataForm.status = user.status != null ? Number(user.status) : 1
|
|||
|
|
dataForm.password = ''
|
|||
|
|
dataForm.comfirmPassword = ''
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function dataFormSubmit (): void {
|
|||
|
|
if (!dataFormRef.value) return
|
|||
|
|
dataFormRef.value.validate((valid) => {
|
|||
|
|
if (!valid) return
|
|||
|
|
const pwdTrim = typeof dataForm.password === 'string' ? dataForm.password.trim() : ''
|
|||
|
|
const payload: Record<string, any> = {
|
|||
|
|
id: dataForm.id || undefined,
|
|||
|
|
username: dataForm.userName,
|
|||
|
|
salt: dataForm.salt,
|
|||
|
|
email: dataForm.email,
|
|||
|
|
mobile: dataForm.mobile,
|
|||
|
|
status: dataForm.status
|
|||
|
|
}
|
|||
|
|
if (dataForm.id) {
|
|||
|
|
if (pwdTrim) payload.password = pwdTrim
|
|||
|
|
} else {
|
|||
|
|
payload.password = pwdTrim
|
|||
|
|
}
|
|||
|
|
if (!dataForm.id) {
|
|||
|
|
if (dataForm.bindScopeType === 'channel_agent') {
|
|||
|
|
payload.orgId = orgBindings.orgId ?? dataForm.selectedOrgId
|
|||
|
|
if (orgBindings.channelOrgId != null) {
|
|||
|
|
payload.channelOrgId = orgBindings.channelOrgId
|
|||
|
|
}
|
|||
|
|
if (orgBindings.agentOrgId) {
|
|||
|
|
payload.agentOrgId = orgBindings.agentOrgId
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
payload.orgId = dataForm.storeOrgId
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
saveUser(payload).then((res) => {
|
|||
|
|
if (res && res.code === 0) {
|
|||
|
|
ElMessage.success({
|
|||
|
|
message: '操作成功',
|
|||
|
|
duration: 1500,
|
|||
|
|
onClose: () => {
|
|||
|
|
visible.value = false
|
|||
|
|
emit('refreshDataList')
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(res?.msg || '请求失败')
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
defineExpose({ init })
|
|||
|
|
</script>
|