This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import { getNewLoginPasswordError } from '@/utils/validation/loginPassword'
|
||||
import type {
|
||||
AgentDistributionRegistrationPageResult,
|
||||
AgentDistributionRegistrationQueryParams,
|
||||
@@ -33,7 +34,7 @@ export interface AgentDistributionRegistrationParams {
|
||||
distribution_code: string // 分销码
|
||||
phone: string // 手机号
|
||||
code: string // 短信验证码
|
||||
password: string // 密码
|
||||
password: string // 8~12 位,大小写字母、数字、半角特殊符号至少三类,无空格和中文
|
||||
shop_name: string // 店铺名称
|
||||
shop_code: string // 店铺编号
|
||||
username: string // 用户名
|
||||
@@ -71,10 +72,12 @@ export class AgentDistributionService extends BaseService {
|
||||
static registerAgent(
|
||||
params: AgentDistributionRegistrationParams
|
||||
): Promise<BaseResponse<AgentDistributionRegistrationResponse>> {
|
||||
const error = getNewLoginPasswordError(params.password)
|
||||
if (error) return Promise.reject(new Error(error))
|
||||
return this.post<BaseResponse<AgentDistributionRegistrationResponse>>(
|
||||
'/api/c/v1/agent-distribution-registrations',
|
||||
params,
|
||||
{ requestOptions: { withToken: false } }
|
||||
{ requestOptions: { withToken: false, errorMessageMode: 'none' } }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
append-to-body
|
||||
:title="t('passwordChange.title')"
|
||||
width="min(420px, calc(100vw - 32px))"
|
||||
:close-on-click-modal="false"
|
||||
|
||||
@@ -53,12 +53,18 @@
|
||||
</div>
|
||||
|
||||
<!-- 列设置 -->
|
||||
<ElPopover placement="bottom" trigger="click">
|
||||
<ElPopover
|
||||
v-model:visible="columnSettingsOpen"
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
@after-enter="startColumnDrag"
|
||||
@before-leave="stopColumnDrag"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="btn"><i class="iconfont-sys" style="font-size: 17px"></i> </div>
|
||||
</template>
|
||||
<div>
|
||||
<VueDraggable v-model="columns">
|
||||
<div ref="columnSettingsRef">
|
||||
<div v-for="item in columns" :key="item.prop || item.type" class="column-option">
|
||||
<div class="drag-icon">
|
||||
<i class="iconfont-sys"></i>
|
||||
@@ -67,7 +73,7 @@
|
||||
item.label || (item.type === 'selection' ? t('table.selection') : '')
|
||||
}}</ElCheckbox>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</div>
|
||||
</ElPopover>
|
||||
<!-- 其他设置 -->
|
||||
@@ -98,8 +104,10 @@
|
||||
import { TableSizeEnum } from '@/enums/formEnum'
|
||||
import { useTableStore } from '@/store/modules/table'
|
||||
import { ElPopover, ElCheckbox } from 'element-plus'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { useDraggable } from 'vue-draggable-plus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { onBeforeUnmount, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -128,6 +136,27 @@
|
||||
|
||||
const columns = defineModel<ColumnOption[]>('columns', { required: true })
|
||||
|
||||
const columnSettingsRef = ref<HTMLElement | null>(null)
|
||||
const columnSettingsOpen = ref(false)
|
||||
// Popover 内容和路由生命周期不同,禁止拖拽库在挂载时用空 ref 自动初始化。
|
||||
const columnDraggable = useDraggable(columnSettingsRef, columns, { immediate: false })
|
||||
|
||||
const startColumnDrag = () => {
|
||||
const element = columnSettingsRef.value
|
||||
if (!columnSettingsOpen.value || !(element instanceof HTMLElement) || !element.isConnected) {
|
||||
return
|
||||
}
|
||||
columnDraggable.start(element)
|
||||
}
|
||||
|
||||
const stopColumnDrag = () => {
|
||||
columnSettingsOpen.value = false
|
||||
columnDraggable.destroy()
|
||||
}
|
||||
|
||||
onBeforeUnmount(stopColumnDrag)
|
||||
onDeactivated(stopColumnDrag)
|
||||
|
||||
// 定义视图模式 model
|
||||
const currentView = defineModel<'table' | 'descriptions'>('currentView', { default: 'table' })
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"newPasswordRequired": "Please enter a new password",
|
||||
"confirmPasswordRequired": "Please enter the new password again",
|
||||
"passwordLength": "Password must be 8-12 characters",
|
||||
"passwordRule": "8-12 characters with uppercase, lowercase and digits; no special characters",
|
||||
"passwordRule": "8-12 characters; at least three of uppercase letters, lowercase letters, digits and special characters. No spaces or Chinese characters.",
|
||||
"passwordMismatch": "The new passwords do not match",
|
||||
"submit": "Change password",
|
||||
"success": "Password changed successfully",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"newPasswordRequired": "请输入新密码",
|
||||
"confirmPasswordRequired": "请再次输入新密码",
|
||||
"passwordLength": "密码长度需为8-12位",
|
||||
"passwordRule": "8~12位,必须包含大写字母、小写字母和数字,不允许特殊符号",
|
||||
"passwordRule": "8~12位,大写字母、小写字母、数字、特殊符号至少包含三类,不允许空格和中文",
|
||||
"passwordMismatch": "两次输入的新密码不一致",
|
||||
"submit": "确认修改",
|
||||
"success": "密码修改成功",
|
||||
|
||||
Binary file not shown.
@@ -124,7 +124,7 @@ export interface AgentQueryParams extends PaginationParams {
|
||||
// 创建账号参数(匹配后端 ModelCreateAccountRequest)
|
||||
export interface CreatePlatformAccountParams {
|
||||
username: string // 用户名 (3-50字符)
|
||||
password: string // 8~12位,必须包含大写、小写和数字,禁止特殊符号
|
||||
password: string // 8~12位,大写、小写、数字、特殊符号至少三类,无空格或中文
|
||||
phone: string // 手机号 (11位)
|
||||
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
|
||||
enterprise_id?: number | null // 关联企业ID(企业账号必填)
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface LoginData {
|
||||
export interface UserInfo {
|
||||
id: number // 用户ID
|
||||
username: string // 用户名
|
||||
name?: string | null // me 接口返回的员工姓名
|
||||
phone: string // 手机号
|
||||
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
|
||||
user_type_name: string // 用户类型名称
|
||||
|
||||
16
src/types/components.d.ts
vendored
16
src/types/components.d.ts
vendored
@@ -92,16 +92,26 @@ declare module 'vue' {
|
||||
CreateRefundDialog: typeof import('./../components/business/CreateRefundDialog.vue')['default']
|
||||
CustomerAccountDialog: typeof import('./../components/business/CustomerAccountDialog.vue')['default']
|
||||
DetailPage: typeof import('./../components/common/DetailPage.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
@@ -114,16 +124,22 @@ declare module 'vue' {
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ElWatermark: typeof import('element-plus/es')['ElWatermark']
|
||||
ExportTaskCreateDialog: typeof import('./../components/business/ExportTaskCreateDialog.vue')['default']
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LocationQueryRaw } from 'vue-router'
|
||||
export interface UserInfo {
|
||||
id: number // 用户ID
|
||||
username: string // 用户名
|
||||
name?: string | null // me 接口返回的员工姓名
|
||||
phone: string // 手机号
|
||||
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
|
||||
user_type_name: string // 用户类型名称
|
||||
|
||||
24
src/utils/business/agentRegistrationError.ts
Normal file
24
src/utils/business/agentRegistrationError.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { normalizeApiError } from './apiError'
|
||||
|
||||
const asRecord = (value: unknown): Record<string, unknown> | undefined =>
|
||||
value !== null && typeof value === 'object' ? (value as Record<string, unknown>) : undefined
|
||||
|
||||
// 仅对公开代理注册隐藏企微场景/模板细节,后台管理和其他接口仍展示原错误。
|
||||
export const getAgentRegistrationFailureMessage = (value: unknown): string => {
|
||||
const root = asRecord(value)
|
||||
const payload =
|
||||
asRecord(asRecord(root?.response)?.data) ??
|
||||
(root?.code !== undefined ? root : (asRecord(root?.data) ?? root))
|
||||
if (
|
||||
payload?.code === 1050 ||
|
||||
payload?.code === 1001 ||
|
||||
payload?.code === '1050' ||
|
||||
payload?.code === '1001'
|
||||
) {
|
||||
return '注册失败, 请联系管理员'
|
||||
}
|
||||
for (const message of [payload?.msg, payload?.message]) {
|
||||
if (typeof message === 'string' && message.trim()) return message
|
||||
}
|
||||
return normalizeApiError(value).message
|
||||
}
|
||||
34
src/utils/business/employeeCollectionActing.ts
Normal file
34
src/utils/business/employeeCollectionActing.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { EmployeeCollectionDebtorSnapshot } from '@/types/api/employeeCollection'
|
||||
|
||||
interface ResponsibleBill {
|
||||
debtor_account_id?: number | null
|
||||
debtor_snapshot?: EmployeeCollectionDebtorSnapshot | null
|
||||
}
|
||||
|
||||
interface CurrentEmployee {
|
||||
id?: number
|
||||
name?: string | null
|
||||
user_type?: number
|
||||
}
|
||||
|
||||
const normalizedName = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')
|
||||
|
||||
export const isEmployeeCollectionBillSelf = (
|
||||
bill: ResponsibleBill | undefined,
|
||||
employee: CurrentEmployee
|
||||
): boolean => {
|
||||
const employeeName = normalizedName(employee.name)
|
||||
const debtorName = normalizedName(bill?.debtor_snapshot?.account_name)
|
||||
// 用户确认:账单负责员工与 me.user.name 同名时,本人办理,不发送代办原因。
|
||||
if (employeeName && debtorName && employeeName === debtorName) return true
|
||||
const debtorId = Number(bill?.debtor_snapshot?.account_id ?? bill?.debtor_account_id)
|
||||
const employeeId = Number(employee.id)
|
||||
return debtorId > 0 && employeeId > 0 && debtorId === employeeId
|
||||
}
|
||||
|
||||
export const requiresEmployeeCollectionActingReason = (
|
||||
bills: Array<ResponsibleBill | undefined>,
|
||||
employee: CurrentEmployee
|
||||
): boolean =>
|
||||
Number(employee.user_type) === 1 &&
|
||||
(bills.length === 0 || bills.some((bill) => !isEmployeeCollectionBillSelf(bill, employee)))
|
||||
@@ -1,14 +1,22 @@
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
|
||||
export const NEW_LOGIN_PASSWORD_HINT = '8~12位,必须包含大写字母、小写字母和数字,不允许特殊符号'
|
||||
export const NEW_LOGIN_PASSWORD_HINT =
|
||||
'8~12位,大写字母、小写字母、数字、特殊符号至少包含三类,不允许空格和中文'
|
||||
|
||||
export const getNewLoginPasswordError = (value: unknown): string | undefined => {
|
||||
if (typeof value !== 'string' || value.length < 8 || value.length > 12) {
|
||||
return '密码长度必须为8~12位'
|
||||
}
|
||||
if (/[^A-Za-z0-9]/.test(value)) return '密码只允许英文字母和数字,不允许特殊符号、空格或中文'
|
||||
if (!/[A-Z]/.test(value) || !/[a-z]/.test(value) || !/[0-9]/.test(value)) {
|
||||
return '密码必须同时包含大写字母、小写字母和数字'
|
||||
// 允许所有可打印的半角符号,不将空白、中文、emoji 或控制字符计作特殊符号。
|
||||
if (/[^\x21-\x7E]/.test(value)) return '密码只允许英文字母、数字和半角特殊符号,不允许空格或中文'
|
||||
const categoryCount = [
|
||||
/[A-Z]/.test(value),
|
||||
/[a-z]/.test(value),
|
||||
/[0-9]/.test(value),
|
||||
/[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]/.test(value)
|
||||
].filter(Boolean).length
|
||||
if (categoryCount < 3) {
|
||||
return '密码须包含大写字母、小写字母、数字、特殊符号中的至少三类'
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -47,17 +47,25 @@
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="密码" prop="password">
|
||||
<ElFormItem label="密码" prop="password" class="password-form-item">
|
||||
<ElInput
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入密码(8-32 位)"
|
||||
maxlength="32"
|
||||
placeholder="请输入密码(8~12位)"
|
||||
maxlength="12"
|
||||
autocomplete="new-password"
|
||||
aria-describedby="registration-password-rule"
|
||||
/>
|
||||
<div class="strength-bar" :class="strengthClass">
|
||||
<p id="registration-password-rule" class="password-hint">
|
||||
{{ NEW_LOGIN_PASSWORD_HINT }}
|
||||
</p>
|
||||
<div class="strength-bar" :class="strengthClass" aria-hidden="true">
|
||||
<i v-for="n in 3" :key="n" :class="{ active: passwordStrength >= n }" />
|
||||
</div>
|
||||
<span v-if="form.password" class="password-status" aria-live="polite">
|
||||
{{ passwordStrength === 3 ? '密码符合规则' : '密码尚未符合规则' }}
|
||||
</span>
|
||||
</ElFormItem>
|
||||
|
||||
<div class="form-grid">
|
||||
@@ -129,6 +137,12 @@
|
||||
type AgentDistributionRegistrationParams
|
||||
} from '@/api/modules/agentDistribution'
|
||||
import { regionData } from '@/utils/constants/regionData'
|
||||
import { getAgentRegistrationFailureMessage } from '@/utils/business/agentRegistrationError'
|
||||
import {
|
||||
createNewLoginPasswordRule,
|
||||
getNewLoginPasswordError,
|
||||
NEW_LOGIN_PASSWORD_HINT
|
||||
} from '@/utils/validation/loginPassword'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -180,7 +194,7 @@
|
||||
code: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 8, max: 32, message: '密码长度为 8-32 位', trigger: 'blur' }
|
||||
createNewLoginPasswordRule()
|
||||
],
|
||||
shop_name: [{ required: true, message: '请输入店铺名称', trigger: 'blur' }],
|
||||
username: [
|
||||
@@ -234,14 +248,7 @@
|
||||
const passwordStrength = computed(() => {
|
||||
const pwd = form.password
|
||||
if (!pwd) return 0
|
||||
let score = 0
|
||||
if (pwd.length >= 8) score += 1
|
||||
if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) score += 1
|
||||
if (/\d/.test(pwd)) score += 1
|
||||
if (/[^A-Za-z0-9]/.test(pwd)) score += 1
|
||||
if (score >= 4) return 3
|
||||
if (score >= 2) return 2
|
||||
return 1
|
||||
return getNewLoginPasswordError(pwd) ? 1 : 3
|
||||
})
|
||||
|
||||
const strengthClass = computed(() => {
|
||||
@@ -267,9 +274,9 @@
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!formRef.value) return
|
||||
if (!formRef.value || submitting.value) return
|
||||
formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
if (!valid || submitting.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const params: AgentDistributionRegistrationParams = {
|
||||
@@ -291,10 +298,10 @@
|
||||
if (res.code === 0) {
|
||||
submitted.value = true
|
||||
} else {
|
||||
ElMessage.error('分销码不可用')
|
||||
ElMessage.error(getAgentRegistrationFailureMessage(res))
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('分销码不可用')
|
||||
} catch (error) {
|
||||
ElMessage.error(getAgentRegistrationFailureMessage(error))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
@@ -315,19 +322,19 @@
|
||||
|
||||
<style scoped lang="scss">
|
||||
.agent-registration-page {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 48px 16px 64px;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(160deg, #eef3fb 0%, #f7f9fd 40%, #eef3fb 100%);
|
||||
|
||||
.register-card {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
padding: 36px 36px 28px;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 30px rgb(93 135 255 / 12%);
|
||||
@@ -375,13 +382,14 @@
|
||||
.strength-bar {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
|
||||
i {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--el-border-color-lighter);
|
||||
border-radius: 2px;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&.active {
|
||||
@@ -398,6 +406,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.password-hint,
|
||||
.password-status {
|
||||
width: 100%;
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.password-form-item :deep(.el-form-item__error) {
|
||||
position: static;
|
||||
width: 100%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.region-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -411,7 +434,7 @@
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
padding: 24px 12px 40px;
|
||||
|
||||
.register-card {
|
||||
@@ -429,7 +452,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
:global(.agent-registration-cascader-popper) {
|
||||
width: calc(100vw - 24px) !important;
|
||||
max-width: calc(100vw - 24px) !important;
|
||||
@@ -441,10 +464,10 @@
|
||||
}
|
||||
|
||||
:global(.agent-registration-cascader-popper .el-cascader-menu) {
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 0;
|
||||
width: 0;
|
||||
min-width: 0 !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:global(.agent-registration-cascader-popper .el-cascader-node__label) {
|
||||
|
||||
@@ -315,6 +315,7 @@
|
||||
import { fenToYuan, yuanToFen } from '@/utils/business/format'
|
||||
import { getErrorMessage, toVoucherKeyList } from '@/utils/business'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { requiresEmployeeCollectionActingReason } from '@/utils/business/employeeCollectionActing'
|
||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||
import {
|
||||
BILL_SOURCE_OPTIONS,
|
||||
@@ -404,28 +405,34 @@
|
||||
const dialogTitle = computed(() => (isResubmit.value ? '修改并重新提交核销申请' : '创建核销申请'))
|
||||
const voucherKeys = computed(() => toVoucherKeyList(form.payment_voucher_keys))
|
||||
|
||||
const currentAccountId = computed(() => userStore.info?.id)
|
||||
|
||||
const findBillOption = (billId: number): BillOption | undefined =>
|
||||
selectedBillOptions.value[billId] || candidateBills.value.find((bill) => bill.id === billId)
|
||||
const findBillOption = (billId: number): BillOption | undefined => {
|
||||
const selected = selectedBillOptions.value[billId]
|
||||
const candidate = candidateBills.value.find((bill) => bill.id === billId)
|
||||
if (!selected) return candidate
|
||||
return {
|
||||
...selected,
|
||||
debtor_account_id: candidate?.debtor_account_id ?? selected.debtor_account_id,
|
||||
debtor_snapshot: candidate?.debtor_snapshot ?? selected.debtor_snapshot
|
||||
}
|
||||
}
|
||||
|
||||
const getBillAssetIdentifier = (bill: BillOption): string => {
|
||||
const source = candidateBills.value.find((item) => item.id === bill.id) || bill
|
||||
return source.customer_snapshot?.asset_identifier || source.asset_identifier || '-'
|
||||
}
|
||||
|
||||
const getBillDebtorAccountId = (billId: number): number | null => {
|
||||
const option = findBillOption(billId)
|
||||
return option?.debtor_account_id ?? option?.debtor_snapshot?.account_id ?? null
|
||||
}
|
||||
const isActing = computed(() =>
|
||||
requiresEmployeeCollectionActingReason(
|
||||
selectedBillIds.value.map(findBillOption),
|
||||
userStore.info ?? {}
|
||||
)
|
||||
)
|
||||
|
||||
const isActing = computed(() => {
|
||||
if (!userStore.isSuperAdmin) return false
|
||||
if (!selectedBillIds.value.length) return true
|
||||
return selectedBillIds.value.some((billId) => {
|
||||
const debtorAccountId = getBillDebtorAccountId(billId)
|
||||
return debtorAccountId === null || debtorAccountId !== currentAccountId.value
|
||||
})
|
||||
watch(isActing, (acting) => {
|
||||
if (!acting) {
|
||||
form.acting_reason = ''
|
||||
formRef.value?.clearValidate('acting_reason')
|
||||
}
|
||||
})
|
||||
|
||||
const displayBills = computed<BillOption[]>(() => {
|
||||
@@ -703,8 +710,22 @@
|
||||
form.acting_reason = application.acting_reason || ''
|
||||
|
||||
const allocations = await fetchApplicationAllocations(application.id)
|
||||
allocations.forEach((allocation) => {
|
||||
const allocationBills = await Promise.all(
|
||||
allocations.map(async (allocation) => {
|
||||
const candidate = candidateBills.value.find((bill) => bill.id === allocation.bill_id)
|
||||
if (candidate) return candidate
|
||||
try {
|
||||
const res = await EmployeeCollectionService.getBillById(allocation.bill_id)
|
||||
return res.code === 0 && res.data?.bill ? toBillOption(res.data.bill) : undefined
|
||||
} catch {
|
||||
// 无法取得责任员工信息时保持原保守代办规则,不猜测为本人。
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
)
|
||||
allocations.forEach((allocation, index) => {
|
||||
const option: BillOption = {
|
||||
...allocationBills[index],
|
||||
id: allocation.bill_id,
|
||||
source_no: allocation.bill_source_no,
|
||||
remaining_amount: Math.max(
|
||||
@@ -846,9 +867,9 @@
|
||||
external_transaction_no: form.external_transaction_no.trim(),
|
||||
payment_voucher_keys: toVoucherKeyList(form.payment_voucher_keys),
|
||||
remark: form.remark.trim() || undefined,
|
||||
allocations,
|
||||
acting_reason: isActing.value ? form.acting_reason.trim() : undefined
|
||||
allocations
|
||||
}
|
||||
if (isActing.value) payload.acting_reason = form.acting_reason.trim()
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user