fix: some
All checks were successful
构建并部署前端到测试环境 / deploy (push) Successful in 55s

This commit is contained in:
luo
2026-10-13 11:25:03 +08:00
parent 52268859aa
commit b2a76adc9a
67 changed files with 1982 additions and 200 deletions

View File

@@ -3,6 +3,7 @@
*/
import { BaseService } from '../BaseService'
import { getNewLoginPasswordError } from '@/utils/validation/loginPassword'
import type {
PlatformAccount,
AccountQueryParams,
@@ -48,7 +49,13 @@ export class AccountService extends BaseService {
id: number,
data: Partial<CreatePlatformAccountParams>
): Promise<BaseResponse> {
return this.update(`/api/admin/accounts/${id}`, data)
const payload = { ...data }
if (payload.password === '') delete payload.password
if (payload.password !== undefined) {
const error = getNewLoginPasswordError(payload.password)
if (error) return Promise.reject(new Error(error))
}
return this.update(`/api/admin/accounts/${id}`, payload)
}
/**
@@ -106,7 +113,9 @@ export class AccountService extends BaseService {
* @param password 新密码
*/
static updateAccountPassword(id: number, password: string): Promise<BaseResponse> {
return this.put<BaseResponse>(`/api/admin/accounts/${id}/password`, { password })
const error = getNewLoginPasswordError(password)
if (error) return Promise.reject(new Error(error))
return this.put<BaseResponse>(`/api/admin/accounts/${id}/password`, { new_password: password })
}
/**

View File

@@ -138,8 +138,15 @@ export class ExchangeService extends BaseService {
* POST /api/admin/exchanges/{id}/complete
* @param id 换货单ID
*/
static completeExchange(id: number): Promise<BaseResponse<ExchangeResponse>> {
return this.post<BaseResponse<ExchangeResponse>>(`/api/admin/exchanges/${id}/complete`, {})
static completeExchange(
id: number,
config?: Record<string, any>
): Promise<BaseResponse<ExchangeResponse>> {
return this.post<BaseResponse<ExchangeResponse>>(
`/api/admin/exchanges/${id}/complete`,
{},
config
)
}
/**

View File

@@ -4,7 +4,7 @@
import { BaseService } from '../BaseService'
import type {
Order,
OrderDetail,
OrderQueryParams,
OrderListResponse,
CreateOrderRequest,
@@ -25,8 +25,8 @@ export class OrderService extends BaseService {
* 获取订单详情
* @param id 订单ID
*/
static getOrderById(id: number): Promise<BaseResponse<Order>> {
return this.getOne<Order>(`/api/admin/orders/${id}`)
static getOrderById(id: number): Promise<BaseResponse<OrderDetail>> {
return this.getOne<OrderDetail>(`/api/admin/orders/${id}`)
}
/**

View File

@@ -20,17 +20,23 @@
v-model="formData.old_password"
type="password"
autocomplete="current-password"
maxlength="32"
show-password
:disabled="submitting"
/>
</ElFormItem>
<ElAlert
:title="t('passwordChange.passwordRule')"
type="info"
:closable="false"
style="margin-bottom: 16px"
/>
<ElFormItem prop="new_password" :label="t('passwordChange.newPassword')">
<ElInput
v-model="formData.new_password"
type="password"
autocomplete="new-password"
maxlength="32"
maxlength="12"
:placeholder="t('passwordChange.passwordRule')"
show-password
:disabled="submitting"
/>
@@ -40,7 +46,8 @@
v-model="formData.confirm_password"
type="password"
autocomplete="new-password"
maxlength="32"
maxlength="12"
:placeholder="t('passwordChange.passwordRule')"
show-password
:disabled="submitting"
@keyup.enter="handleSubmit"
@@ -61,12 +68,13 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElAlert, ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { AuthService } from '@/api/modules'
import { useUserStore } from '@/store/modules/user'
import { normalizeApiError } from '@/utils/business/apiError'
import { useI18n } from 'vue-i18n'
import { createNewLoginPasswordRule } from '@/utils/validation/loginPassword'
defineOptions({ name: 'ChangePasswordDialog' })
@@ -90,16 +98,15 @@
const formRules = computed<FormRules>(() => ({
old_password: [
{ required: true, message: t('passwordChange.oldPasswordRequired'), trigger: 'blur' },
{ min: 8, max: 32, message: t('passwordChange.passwordLength'), trigger: 'blur' }
{ required: true, message: t('passwordChange.oldPasswordRequired'), trigger: 'blur' }
],
new_password: [
{ required: true, message: t('passwordChange.newPasswordRequired'), trigger: 'blur' },
{ min: 8, max: 32, message: t('passwordChange.passwordLength'), trigger: 'blur' }
createNewLoginPasswordRule(t('passwordChange.passwordRule'))
],
confirm_password: [
{ required: true, message: t('passwordChange.confirmPasswordRequired'), trigger: 'blur' },
{ min: 8, max: 32, message: t('passwordChange.passwordLength'), trigger: 'blur' },
createNewLoginPasswordRule(t('passwordChange.passwordRule')),
{
validator: (_rule, value: string, callback) => {
if (value && value !== formData.new_password) {
@@ -139,7 +146,7 @@
})
if (response.code !== 0) {
ElMessage.error(response.msg || t('passwordChange.failure'))
ElMessage.error(response.msg || response.message || t('passwordChange.failure'))
return
}
@@ -155,3 +162,11 @@
}
}
</script>
<style scoped lang="scss">
:deep(.el-form-item__error) {
position: static;
width: 100%;
line-height: 1.5;
}
</style>

View File

@@ -19,7 +19,12 @@ export const WITHDRAWAL_STATUS_OPTIONS = [
{ label: '待审核', value: WithdrawalStatus.PENDING, type: 'warning' as const },
{ label: '已通过', value: WithdrawalStatus.APPROVED, type: 'success' as const },
{ label: '已拒绝', value: WithdrawalStatus.REJECTED, type: 'danger' as const },
{ label: '已到账', value: WithdrawalStatus.COMPLETED, type: 'success' as const }
{ label: '已到账', value: WithdrawalStatus.COMPLETED, type: 'success' as const },
{
label: '审批提交失败',
value: WithdrawalStatus.APPROVAL_SUBMISSION_FAILED,
type: 'danger' as const
}
]
// 支付方式选项
@@ -36,9 +41,22 @@ export const WithdrawalStatusMap = {
1: { label: '待审核', type: 'warning' as const, color: '#E6A23C' },
2: { label: '已通过', type: 'success' as const, color: '#67C23A' },
3: { label: '已拒绝', type: 'danger' as const, color: '#F56C6C' },
4: { label: '已到账', type: 'success' as const, color: '#67C23A' }
4: { label: '已到账', type: 'success' as const, color: '#67C23A' },
5: { label: '审批提交失败', type: 'danger' as const, color: '#F56C6C' }
}
export const WITHDRAWAL_QUALIFICATION_APPROVAL_FAILED = 4
export const WITHDRAWAL_QUALIFICATION_STATUS_OPTIONS = [
{ label: '待审批', value: 0 },
{ label: '已通过', value: 1 },
{ label: '已驳回', value: 2 },
{ label: '已作废', value: 3 },
{ label: '审批提交失败', value: WITHDRAWAL_QUALIFICATION_APPROVAL_FAILED }
]
export const canResubmitWithdrawal = (status: number): boolean =>
status === WithdrawalStatus.REJECTED || status === WithdrawalStatus.APPROVAL_SUBMISSION_FAILED
// ========== 佣金状态映射 ==========
// 佣金状态映射 (1:已冻结, 2:解冻中, 3:已发放, 4:已失效, 99:待人工修正)
@@ -134,10 +152,7 @@ export const COMMISSION_RECORD_STATUS_OPTIONS = [
]
// 是否回溯明细行(source 与 status 双判断,兼容未返回 source 的响应)
export function isCommissionClawbackRecord(record: {
source?: string
status?: number
}): boolean {
export function isCommissionClawbackRecord(record: { source?: string; status?: number }): boolean {
return record.source === 'clawback' || record.status === CommissionStatus.CLAWBACK
}

View File

@@ -16,7 +16,8 @@ export const RefundStatusNameMap: Record<RefundStatus, string> = {
[RefundStatus.REJECTED]: '已拒绝',
[RefundStatus.RETURNED]: '已退回',
[RefundStatus.REFUNDING]: '原路退款处理中',
[RefundStatus.REFUND_FAILED]: '原路退款失败'
[RefundStatus.REFUND_FAILED]: '原路退款失败',
[RefundStatus.APPROVAL_SUBMISSION_FAILED]: '审批提交失败'
}
/** 退款状态标签类型映射 */
@@ -27,7 +28,8 @@ export const RefundStatusTypeMap: Record<RefundStatus, 'warning' | 'success' | '
[RefundStatus.REJECTED]: 'danger',
[RefundStatus.RETURNED]: 'info',
[RefundStatus.REFUNDING]: 'warning',
[RefundStatus.REFUND_FAILED]: 'danger'
[RefundStatus.REFUND_FAILED]: 'danger',
[RefundStatus.APPROVAL_SUBMISSION_FAILED]: 'danger'
}
/** 退款方式名称映射 */
@@ -73,7 +75,12 @@ export const canResubmitRefund = (item: {
anomaly_flag?: number
}): boolean => {
if (!item.status) return false
if (item.status === RefundStatus.REJECTED || item.status === RefundStatus.RETURNED) return true
if (
item.status === RefundStatus.REJECTED ||
item.status === RefundStatus.RETURNED ||
item.status === RefundStatus.APPROVAL_SUBMISSION_FAILED
)
return true
if (item.status === RefundStatus.REFUND_FAILED) return item.anomaly_flag !== 1
return false
}
@@ -85,3 +92,9 @@ export const canManualApproveRefund = (item: {
status?: RefundStatus
approval_instance_id?: number | null
}): boolean => item.status === RefundStatus.PENDING && !item.approval_instance_id
export const canRecoverRefundApproval = (item: {
status?: RefundStatus
approval_recoverable?: boolean
}): boolean =>
item.status !== RefundStatus.APPROVAL_SUBMISSION_FAILED && item.approval_recoverable === true

View File

@@ -26,7 +26,8 @@
"oldPasswordRequired": "Please enter your current password",
"newPasswordRequired": "Please enter a new password",
"confirmPasswordRequired": "Please enter the new password again",
"passwordLength": "Password must be 8-32 characters",
"passwordLength": "Password must be 8-12 characters",
"passwordRule": "8-12 characters with uppercase, lowercase and digits; no special characters",
"passwordMismatch": "The new passwords do not match",
"submit": "Change password",
"success": "Password changed successfully",

View File

@@ -27,7 +27,8 @@
"oldPasswordRequired": "请输入当前密码",
"newPasswordRequired": "请输入新密码",
"confirmPasswordRequired": "请再次输入新密码",
"passwordLength": "密码长度需为8-32位",
"passwordLength": "密码长度需为8-12位",
"passwordRule": "8~12位,必须包含大写字母、小写字母和数字,不允许特殊符号",
"passwordMismatch": "两次输入的新密码不一致",
"submit": "确认修改",
"success": "密码修改成功",

View File

@@ -124,7 +124,7 @@ export interface AgentQueryParams extends PaginationParams {
// 创建账号参数(匹配后端 ModelCreateAccountRequest)
export interface CreatePlatformAccountParams {
username: string // 用户名 (3-50字符)
password: string // 密码 (8-32字符)
password: string // 8~12位,必须包含大写、小写和数字,禁止特殊符号
phone: string // 手机号 (11位)
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
enterprise_id?: number | null // 关联企业ID(企业账号必填)

View File

@@ -3,7 +3,7 @@
export interface AgentDistributionRegistrationQueryParams {
page?: number
page_size?: number
status?: 0 | 1 | 2
status?: 0 | 1 | 2 | 3
start_time?: string
end_time?: string
decided_start_time?: string
@@ -54,3 +54,17 @@ export interface RecoverAgentDistributionRegistrationResponse {
id: number
message?: string
}
export const AGENT_REGISTRATION_APPROVAL_FAILED_STATUS = 3
export const canRecoverAgentRegistration = (
item?: AgentDistributionRegistrationItem | null
): boolean =>
!!item && item.status !== AGENT_REGISTRATION_APPROVAL_FAILED_STATUS && item.recoverable === true
export const getAgentRegistrationStatusName = (item: AgentDistributionRegistrationItem): string =>
item.status_name ||
({ 0: '待审批', 1: '已通过', 2: '已驳回', 3: '审批提交失败' } as Record<number, string>)[
item.status
] ||
String(item.status)

View File

@@ -9,7 +9,8 @@ export enum AgentRechargeStatus {
COMPLETED = 3, // 已完成
CLOSED = 4, // 已关闭
REFUNDED = 5, // 已退款
REJECTED = 6 // 已驳回
REJECTED = 6, // 已驳回
APPROVAL_SUBMISSION_FAILED = 7 // 审批提交失败(终态)
}
// 支付方式

View File

@@ -137,7 +137,8 @@ export enum WithdrawalStatus {
PENDING = 1, // 待审核
APPROVED = 2, // 已通过
REJECTED = 3, // 已拒绝
COMPLETED = 4 // 已到账
COMPLETED = 4, // 已到账
APPROVAL_SUBMISSION_FAILED = 5 // 审批提交失败(终态)
}
// 佣金类型

View File

@@ -6,6 +6,7 @@
export interface BaseResponse<T = any> {
code: number
msg: string
message?: string
data: T
timestamp?: string
}

View File

@@ -183,7 +183,7 @@ export type CloseEmployeeCollectionBillResult = EmployeeCollectionBill
// ========== 核销申请 ==========
/** 申请状态:0 审批中 / 1 已通过 / 2 已驳回 / 3 已关闭 */
/** 申请状态:0 审批中 / 1 已通过 / 2 已驳回 / 3 已关闭 / 4 审批提交失败 */
export type EmployeeCollectionApplicationStatus = number
/** 审批尝试记录(历史材料不被覆盖) */

View File

@@ -10,6 +10,28 @@ export enum PaymentStatus {
REFUNDED = 4 // 已退款
}
// 支付单状态与订单支付状态数值不同,必须独立处理。
export enum OrderPaymentRecordStatus {
PENDING = 0,
PAID = 1,
FAILED = 2,
REFUNDED = 3
}
export interface OrderPaymentRecord {
payment_no: string
payment_method: 'wechat' | 'alipay'
payment_channel: string
payment_merchant_name: string
third_party_trade_no: string
amount: number // 分
status: OrderPaymentRecordStatus
status_name: string
created_at: string
paid_at: string | null
expire_at: string | null
}
// 订单类型
export type OrderType = 'single_card' | 'device'
@@ -68,6 +90,9 @@ export interface Order {
payment_status: PaymentStatus
payment_status_text: string
payment_method: OrderPaymentMethod
third_party_trade_no: string
payment_channel: string
payment_merchant_name: string
total_amount: number // 订单总金额(分)
actual_paid_amount?: number // 实付金额(分)
paid_at: string | null
@@ -97,6 +122,10 @@ export interface Order {
updated_at: string
}
export interface OrderDetail extends Order {
payments: OrderPaymentRecord[]
}
// 订单查询参数
export interface OrderQueryParams {
page?: number

View File

@@ -19,7 +19,6 @@ export interface PackageTrafficAlertRuleItem {
id: number // 预警规则ID
package_id: number // 套餐商品ID
package_name: string // 套餐名称
real_data_mb: number // 套餐商品当前真流量额度(MB),仅用于配置校验展示,不作为预警分母
threshold_percent: number // 真流量预警阈值百分比(1~100,允许两位小数)
enabled: boolean // 是否启用
enabled_name: string // 启用状态名称(中文)
@@ -90,6 +89,9 @@ export interface PackageTrafficAlertRecordItem {
asset_type?: PackageTrafficAlertAssetType | null // 资产类型快照
asset_identifier?: string | null // 资产或卡标识快照
threshold_percent?: number // 触发阈值快照
threshold_mb?: number | null // 触发时阈值流量(MB),主套餐与加油包汇总
used_mb?: number | null // 触发时已用流量(MB),含加油包
limit_mb?: number | null // 触发时总额度(MB),含加油包
triggered_at?: string // 触发时间
notification_status?: PackageTrafficAlertNotificationStatus // 通知投递结果
notification_status_name?: string // 通知投递结果名称

View File

@@ -9,7 +9,8 @@ export enum RefundStatus {
REJECTED = 3, // 已拒绝
RETURNED = 4, // 已退回
REFUNDING = 5, // 原路退款处理中
REFUND_FAILED = 6 // 原路退款失败
REFUND_FAILED = 6, // 原路退款失败
APPROVAL_SUBMISSION_FAILED = 7 // 审批提交失败(终态)
}
// 退款方式

View File

@@ -92,27 +92,16 @@ 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']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
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']
@@ -125,26 +114,16 @@ 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']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
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']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
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']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
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']

View File

@@ -23,7 +23,7 @@ export const normalizeApiError = (error: unknown): NormalizedApiError => {
const response = getResponse(error)
const status = response?.status
const code = response?.data?.code
const backendMessage = response?.data?.msg
const backendMessage = response?.data?.msg || response?.data?.message
const timeout = axios.isAxiosError(error) && error.code === 'ECONNABORTED'
if (timeout) {

View File

@@ -0,0 +1,24 @@
type BusinessFailure = {
code?: number | string
msg?: string
message?: string
data?: { phone?: unknown }
}
const getBusinessFailure = (value: unknown): BusinessFailure | undefined => {
if (!value || typeof value !== 'object') return undefined
const candidate = value as { response?: { data?: BusinessFailure } } & BusinessFailure
return candidate.response?.data || candidate
}
export const getExchangePhoneConflictMessage = (value: unknown): string | undefined => {
const failure = getBusinessFailure(value)
if (Number(failure?.code) !== 1202) return undefined
const phone = typeof failure?.data?.phone === 'string' ? failure.data.phone.trim() : ''
return `新资产已绑定手机号${phone ? ` ${phone}` : ''},请先到『手机号资产关联』解除后再提交`
}
export const getExchangeBusinessErrorMessage = (value: unknown): string | undefined => {
const failure = getBusinessFailure(value)
return failure?.msg || failure?.message
}

View File

@@ -0,0 +1,29 @@
import { OrderPaymentRecordStatus } from '@/types/api/order'
const CHANNEL_NAMES: Record<string, string> = {
wechat: '微信直连 v3',
wechat_v2: '微信直连 v2',
fuiou: '富友'
}
const PAYMENT_STATUS_NAMES: Record<OrderPaymentRecordStatus, string> = {
[OrderPaymentRecordStatus.PENDING]: '待支付',
[OrderPaymentRecordStatus.PAID]: '已支付',
[OrderPaymentRecordStatus.FAILED]: '已失败',
[OrderPaymentRecordStatus.REFUNDED]: '已退款'
}
export const getOrderPaymentChannelName = (channel?: string | null): string =>
channel ? CHANNEL_NAMES[channel] || channel : '-'
export const getOrderPaymentRecordStatusName = (
status: OrderPaymentRecordStatus,
name?: string | null
): string => name || PAYMENT_STATUS_NAMES[status] || String(status)
export const getOrderPaymentRecordTagType = (status: OrderPaymentRecordStatus) => {
if (status === OrderPaymentRecordStatus.PAID) return 'success'
if (status === OrderPaymentRecordStatus.FAILED) return 'danger'
if (status === OrderPaymentRecordStatus.PENDING) return 'warning'
return 'info'
}

View File

@@ -0,0 +1,3 @@
// UTF-8 BOM 确保 Excel 打开中文 CSV 时正确识别编码。
export const SHOP_BUSINESS_OWNER_CSV_TEMPLATE =
'\uFEFF店铺编码,操作类型,平台登录账号,备注\r\n,仅支持换绑或清空,换绑填写平台用户登录手机号(清空留空),\r\n'

View File

@@ -0,0 +1,2 @@
export const formatTrafficAlertMb = (value?: number | null): string =>
value == null || !Number.isFinite(value) ? '-' : value.toFixed(2)

View File

@@ -0,0 +1,24 @@
import type { FormItemRule } from 'element-plus'
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 '密码必须同时包含大写字母、小写字母和数字'
}
return undefined
}
// 必填由调用表单决定,空值不妨碍不修改密码的账号编辑。
export const createNewLoginPasswordRule = (message?: string): FormItemRule => ({
validator: (_rule, value, callback) => {
if (value === '' || value === undefined || value === null) return callback()
const error = getNewLoginPasswordError(value)
callback(error ? new Error(message || error) : undefined)
},
trigger: 'blur'
})

View File

@@ -56,7 +56,8 @@
<ElInput
v-model="formData.password"
type="password"
placeholder="请输入密码"
:placeholder="NEW_LOGIN_PASSWORD_HINT"
maxlength="12"
show-password
/>
</ElFormItem>
@@ -307,6 +308,10 @@
</template>
<script setup lang="ts">
import {
createNewLoginPasswordRule,
NEW_LOGIN_PASSWORD_HINT
} from '@/utils/validation/loginPassword'
import { h, onActivated } from 'vue'
import { useRoute } from 'vue-router'
import {
@@ -1043,7 +1048,7 @@
username: [{ required: true, message: '请输入账号名称', trigger: 'blur' }],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
createNewLoginPasswordRule()
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
@@ -1101,6 +1106,10 @@
}
const createRes = await AccountService.createAccount(data)
if (createRes.code !== 0) {
ElMessage.error(createRes.msg || createRes.message || '账号创建失败')
return
}
// 创建成功后分配角色(如果选择了)
if (createRes.code === 0) {
@@ -1124,14 +1133,18 @@
phone: formData.phone
}
await AccountService.updateAccount(Number(formData.id), data)
const updateRes = await AccountService.updateAccount(Number(formData.id), data)
if (updateRes.code !== 0) {
ElMessage.error(updateRes.msg || updateRes.message || '账号修改失败')
return
}
ElMessage.success('更新成功')
}
dialogVisible.value = false
await getAccountList()
} catch (error) {
console.error(error)
} catch {
// HTTP 层已展示后端原因,不输出可能包含密码的请求错误对象。
}
}
})

View File

@@ -154,7 +154,8 @@
<ElInput
v-model="form.password"
type="password"
placeholder="请输入登录密码"
:placeholder="NEW_LOGIN_PASSWORD_HINT"
maxlength="12"
show-password
/>
</ElFormItem>
@@ -178,7 +179,8 @@
<ElInput
v-model="passwordForm.password"
type="password"
placeholder="请输入新密码(6-20位)"
:placeholder="NEW_LOGIN_PASSWORD_HINT"
maxlength="12"
show-password
/>
</ElFormItem>
@@ -202,6 +204,10 @@
</template>
<script setup lang="ts">
import {
createNewLoginPasswordRule,
NEW_LOGIN_PASSWORD_HINT
} from '@/utils/validation/loginPassword'
import { h, onActivated } from 'vue'
import { useRouter } from 'vue-router'
import { RoutesAlias } from '@/router/routesAlias'
@@ -388,14 +394,14 @@
login_phone: [{ required: true, message: '请输入登录手机号', trigger: 'blur' }],
password: [
{ required: true, message: '请输入登录密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }
createNewLoginPasswordRule()
]
})
const passwordRules = reactive<FormRules>({
password: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }
createNewLoginPasswordRule()
]
})
@@ -799,7 +805,11 @@
if (dialogType.value === 'add') {
data.login_phone = form.login_phone
data.password = form.password
await EnterpriseService.createEnterprise(data)
const res = await EnterpriseService.createEnterprise(data)
if (res.code !== 0) {
ElMessage.error(res.msg || res.message || '新增企业失败')
return
}
ElMessage.success('新增成功')
} else {
await EnterpriseService.updateEnterprise(form.id, data)
@@ -809,8 +819,8 @@
dialogVisible.value = false
formEl.resetFields()
await getTableData()
} catch (error) {
console.error(error)
} catch {
// HTTP 层统一提示,不记录带密码的请求错误对象。
} finally {
submitLoading.value = false
}
@@ -826,14 +836,18 @@
if (valid) {
passwordSubmitLoading.value = true
try {
await EnterpriseService.updateEnterprisePassword(currentEnterpriseId.value, {
const res = await EnterpriseService.updateEnterprisePassword(currentEnterpriseId.value, {
password: passwordForm.password
})
if (res.code !== 0) {
ElMessage.error(res.msg || res.message || '密码修改失败')
return
}
ElMessage.success('密码修改成功')
passwordDialogVisible.value = false
formEl.resetFields()
} catch (error) {
console.error(error)
} catch {
// HTTP 层已提示,不输出密码。
} finally {
passwordSubmitLoading.value = false
}

View File

@@ -442,6 +442,12 @@
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
import { resolveAuditResourceTarget } from '@/utils/business/auditNavigation'
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
import { AUGUST_PERMISSIONS } from '@/config/constants/augustIteration'
import { normalizeApiError } from '@/utils/business/apiError'
import {
getExchangePhoneConflictMessage,
getExchangeBusinessErrorMessage
} from '@/utils/business/exchangeError'
defineOptions({ name: 'ExchangeManagement' })
@@ -449,6 +455,38 @@
const userStore = useUserStore()
const router = useRouter()
const handleExchangeFailure = async (failure: unknown, fallback: string) => {
const conflictMessage = getExchangePhoneConflictMessage(failure)
if (!conflictMessage) {
const normalized = normalizeApiError(failure)
if (normalized.kind === 'auth') return // 登录失效由统一 HTTP 层反馈。
ElMessage.error(
getExchangeBusinessErrorMessage(failure) ||
(normalized.kind === 'unknown' ? fallback : normalized.message)
)
return
}
const canManageAssociations = hasAuth(AUGUST_PERMISSIONS.phoneAssetAssociation.list)
try {
await ElMessageBox.confirm(
canManageAssociations
? conflictMessage
: `${conflictMessage}。您暂无管理权限,请联系管理员解除。`,
'新资产手机号绑定冲突',
{
type: 'warning',
confirmButtonText: canManageAssociations ? '前往手机号资产关联' : '知道了',
cancelButtonText: '暂不处理',
showCancelButton: canManageAssociations,
dangerouslyUseHTMLString: false
}
)
if (canManageAssociations) await router.push(RoutesAlias.PhoneAssetAssociation)
} catch {
// 用户关闭提示,不执行解绑或改写换货状态。
}
}
const currentExchangeRow = ref<ExchangeResponse | null>(null)
const loading = ref(false)
const shipDialogVisible = ref(false)
@@ -871,7 +909,7 @@
label: '换货单号',
width: 230,
showOverflowTooltip: true,
fixed:true,
fixed: true,
formatter: (row: any) =>
h(
ElButton,
@@ -1535,7 +1573,9 @@
requestData.migrate_data = createForm.migrate_data
}
const res = await ExchangeService.createExchange(requestData)
const res = await ExchangeService.createExchange(requestData, {
requestOptions: { errorMessageMode: 'none' }
})
if (res.code === 0) {
ElMessage.success('换货单创建成功')
createDialogVisible.value = false
@@ -1543,9 +1583,9 @@
return
}
ElMessage.error(res.msg || '换货单创建失败')
await handleExchangeFailure(res, '换货单创建失败')
} catch (error) {
console.error('创建换货单失败:', error)
await handleExchangeFailure(error, '换货单创建失败')
} finally {
createLoading.value = false
}
@@ -1767,48 +1807,36 @@
}
// 确认完成
const handleCompleteExchange = (row: ExchangeResponse) => {
const handleCompleteExchange = async (row: ExchangeResponse) => {
if (completeLoadingId.value === row.id) return
completeLoadingId.value = row.id
ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
beforeClose: async (action, instance, done) => {
if (action !== 'confirm') {
done()
return
}
instance.confirmButtonLoading = true
instance.confirmButtonText = '提交中...'
try {
const res = await ExchangeService.completeExchange(row.id)
if (res.code === 0) {
await refreshExchangeDetail(row.id)
await loadExchangeList()
ElMessage.success('操作成功')
done()
return
}
ElMessage.error(res.msg || '确认完成失败')
} catch (error) {
console.error('确认完成失败:', error)
} finally {
instance.confirmButtonLoading = false
instance.confirmButtonText = '确定'
}
}
})
.then(() => {})
.finally(() => {
completeLoadingId.value = null
try {
await ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.catch(() => {})
} catch {
completeLoadingId.value = null
return
}
try {
const res = await ExchangeService.completeExchange(row.id, {
requestOptions: { errorMessageMode: 'none' }
})
// 刷新失败也不能吞掉原操作的 1202 业务响应。
await Promise.allSettled([refreshExchangeDetail(row.id), loadExchangeList()])
if (res.code === 0) ElMessage.success('操作成功')
else await handleExchangeFailure(res, '确认完成失败')
} catch (error) {
// 仅重新读取原单状态,不乐观标记完成,错误对象可能包含完整手机号,不输出日志。
await Promise.allSettled([refreshExchangeDetail(row.id), loadExchangeList()])
await handleExchangeFailure(error, '确认完成失败')
} finally {
completeLoadingId.value = null
}
}
// 旧资产转新

View File

@@ -62,11 +62,11 @@
<p>2. 列格式请设置为文本格式,避免长数字被转为科学计数法。</p>
<p>3. <strong>列顺序固定,不可调整。</strong>系统按位置读取,不识别列名。</p>
<p style="color: var(--el-color-primary)">
4. 必填列:设备标识(第 1 列,支持 VirtualNo、IMEI 或 SN)。
4. 必填列:设备标识(第 1 列,支持 VirtualNo、IMEI 或 SN)和独立的 IMEI
列。即使设备标识填写 IMEI,IMEI 列也不能留空或仅填空格。
</p>
<p>
5. 可选列:SN、设备名称、设备型号、设备类型、IMEI、制造商、最大 SIM 槽数、卡 1~卡 4
ICCID。
5. 可选列:SN、设备名称、设备型号、设备类型、制造商、最大 SIM 槽数、卡 1~卡 4 ICCID。
</p>
</div>
</template>
@@ -162,7 +162,6 @@
} from '@/types/api/device'
import { RoutesAlias } from '@/router/routesAlias'
import { generatePackageCode } from '@/utils/codeGenerator'
import { normalizeApiError } from '@/utils/business/apiError'
defineOptions({ name: 'DeviceTask' })

View File

@@ -146,6 +146,14 @@
</ElTabPane>
<!-- 提现资料资格 -->
<ElTabPane label="提现资料" name="qualification">
<ArtSearchBar
:filter="qualificationSearchForm"
@update:filter="Object.assign(qualificationSearchForm, $event)"
:items="qualificationSearchItems"
:show-expand="false"
@search="handleQualificationSearch"
@reset="handleQualificationReset"
/>
<ArtTableHeader
:columnList="qualificationColumnOptions"
v-model:columns="qualificationColumnChecks"
@@ -172,7 +180,7 @@
:pageSize="qualificationPagination.pageSize"
:total="qualificationPagination.total"
:actions="getQualificationActions"
:actionsWidth="100"
:actionsWidth="160"
:marginTop="10"
:height="500"
@size-change="handleQualificationSizeChange"
@@ -424,7 +432,20 @@
<ElDescriptionsItem label="提现单号">{{
withdrawalDetail.withdrawal_no
}}</ElDescriptionsItem>
<ElDescriptionsItem label="状态">{{ withdrawalDetail.status_name }}</ElDescriptionsItem>
<ElDescriptionsItem label="状态"
><ElTag
:type="
WithdrawalStatusMap[withdrawalDetail.status as keyof typeof WithdrawalStatusMap]
?.type || 'info'
"
>{{
withdrawalDetail.status_name ||
WithdrawalStatusMap[withdrawalDetail.status as keyof typeof WithdrawalStatusMap]
?.label ||
'-'
}}</ElTag
></ElDescriptionsItem
>
<ElDescriptionsItem label="提现金额">{{
formatMoney(withdrawalDetail.amount)
}}</ElDescriptionsItem>
@@ -577,6 +598,9 @@
import type { Order, OrderQueryParams } from '@/types/api/order'
import {
WithdrawalStatusMap,
canResubmitWithdrawal,
WITHDRAWAL_QUALIFICATION_APPROVAL_FAILED,
WITHDRAWAL_QUALIFICATION_STATUS_OPTIONS,
WithdrawalMethodMap,
COMMISSION_RECORD_SOURCE_MAP,
COMMISSION_RECORD_STATUS_OPTIONS,
@@ -588,11 +612,13 @@
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useUserStore } from '@/store/modules/user'
import { useAuth } from '@/composables/useAuth'
import { formatDateTime, formatMoney, yuanToFen } from '@/utils/business/format'
defineOptions({ name: 'MyCommission' })
const userStore = useUserStore()
const { hasAuth } = useAuth()
const isSuperAdmin = computed(() => userStore.isSuperAdmin)
@@ -1021,7 +1047,8 @@
{ label: '待审核', value: 1 },
{ label: '已通过', value: 2 },
{ label: '已拒绝', value: 3 },
{ label: '已到账', value: 4 }
{ label: '已到账', value: 4 },
{ label: '审批提交失败', value: WithdrawalStatus.APPROVAL_SUBMISSION_FAILED }
],
config: {
clearable: true,
@@ -1186,6 +1213,7 @@
// 刷新表格
const handleWithdrawalRefresh = () => {
getWithdrawalList()
loadSummary()
}
// 处理表格分页变化
@@ -1429,6 +1457,24 @@
const qualificationLoading = ref(false)
const qualificationTableRef = ref()
const qualificationList = ref<WithdrawalQualificationItem[]>([])
const qualificationSearchForm = reactive({ status: undefined as number | undefined })
const qualificationSearchItems: SearchFormItem[] = [
{
label: '资料状态',
prop: 'status',
type: 'select',
options: WITHDRAWAL_QUALIFICATION_STATUS_OPTIONS,
config: { clearable: true, placeholder: '全部' }
}
]
const handleQualificationSearch = () => {
qualificationPagination.page = 1
void getQualificationList()
}
const handleQualificationReset = () => {
qualificationSearchForm.status = undefined
handleQualificationSearch()
}
const qualificationPagination = reactive({
page: 1,
pageSize: 10,
@@ -1469,7 +1515,15 @@
width: 120,
formatter: (row: WithdrawalQualificationItem) => {
const tagType = row.status === 0 ? 'warning' : row.status === 1 ? 'success' : 'danger'
return h(ElTag, { type: tagType }, () => row.status_name || String(row.status))
return h(
ElTag,
{ type: tagType },
() =>
row.status_name ||
WITHDRAWAL_QUALIFICATION_STATUS_OPTIONS.find((item) => item.value === row.status)
?.label ||
String(row.status)
)
}
},
{
@@ -1512,7 +1566,8 @@
try {
const res = await CommissionService.getWithdrawalQualifications(currentShopId.value, {
page: qualificationPagination.page,
page_size: qualificationPagination.pageSize
page_size: qualificationPagination.pageSize,
status: qualificationSearchForm.status
})
if (res.code === 0) {
qualificationList.value = res.data.items || []
@@ -1632,6 +1687,17 @@
}
const getQualificationActions = (row: WithdrawalQualificationItem) => {
if (row.status === WITHDRAWAL_QUALIFICATION_APPROVAL_FAILED) {
return hasAuth('my_commission:add')
? [
{
label: '重新提交资料',
handler: () => showQualificationDialog(),
type: 'primary' as const
}
]
: []
}
if (!userStore.isSuperAdmin) return []
return [
{
@@ -1699,7 +1765,7 @@
type: 'primary'
}
]
if (row.status === WithdrawalStatus.REJECTED) {
if (canResubmitWithdrawal(row.status) && hasAuth('my_commission:add')) {
actions.push({
label: '重新提交',
handler: () => openResubmitDialog(row),
@@ -1781,6 +1847,7 @@
})
const openResubmitDialog = (row: WithdrawalRequestItem) => {
if (!canResubmitWithdrawal(row.status) || !hasAuth('my_commission:add')) return
resubmitTargetId.value = row.id
resubmitForm.amount = Number((row.amount / 100).toFixed(2))
resubmitForm.withdrawal_method = row.withdrawal_method || WithdrawalMethod.ALIPAY
@@ -1812,11 +1879,15 @@
if (resubmitForm.invoice_keys.length > 0) {
params.invoice_keys = resubmitForm.invoice_keys
}
await CommissionService.resubmitWithdrawalRequest(
const res = await CommissionService.resubmitWithdrawalRequest(
currentShopId.value!,
resubmitTargetId.value!,
params
)
if (res.code !== 0) {
ElMessage.error(res.msg || res.message || '重新提交失败')
return
}
ElMessage.success('重新提交成功')
resubmitDialogVisible.value = false
await getWithdrawalList()

View File

@@ -75,6 +75,7 @@
</template>
<script setup lang="ts">
import { WithdrawalStatus as WithdrawalStatusValue } from '@/types/api/commission'
import { h } from 'vue'
import { CommissionService, ShopService } from '@/api/modules'
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
@@ -121,7 +122,8 @@
{ label: '待审核', value: 1 },
{ label: '已通过', value: 2 },
{ label: '已拒绝', value: 3 },
{ label: '已到账', value: 4 }
{ label: '已到账', value: 4 },
{ label: '审批提交失败', value: WithdrawalStatusValue.APPROVAL_SUBMISSION_FAILED }
]
// 搜索表单配置

View File

@@ -69,7 +69,8 @@
<ElInput
v-model="accountForm.password"
type="password"
placeholder="请输入密码"
:placeholder="NEW_LOGIN_PASSWORD_HINT"
maxlength="12"
show-password
/>
</ElFormItem>
@@ -225,6 +226,10 @@
</template>
<script setup lang="ts">
import {
createNewLoginPasswordRule,
NEW_LOGIN_PASSWORD_HINT
} from '@/utils/validation/loginPassword'
import { h, computed, ref, reactive, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
@@ -584,7 +589,7 @@
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度为 6-20 个字符', trigger: 'blur' }
createNewLoginPasswordRule()
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
@@ -631,15 +636,18 @@
data.enterprise_id = entityId
}
await AccountService.createAccount(data)
const res = await AccountService.createAccount(data)
if (res.code !== 0) {
ElMessage.error(res.msg || res.message || '添加账号失败')
return
}
ElMessage.success('添加账号成功')
dialogVisible.value = false
// 刷新列表
await getTableData()
} catch (error) {
console.error(error)
console.log('添加账号失败')
} catch {
// HTTP 层已提示,不输出包含新密码的请求错误对象。
} finally {
submitLoading.value = false
}

View File

@@ -13,6 +13,8 @@ interface BuildAgentRechargeActionsOptions {
onConfirmPayment: (row: AgentRecharge) => void
onReject: (row: AgentRecharge) => void
onTriggerApproval: (row: AgentRecharge) => void
canCreateOffline?: boolean
onRestart?: (row: AgentRecharge) => void
}
export const buildAgentRechargeActions = (
@@ -31,7 +33,8 @@ export const buildAgentRechargeActions = (
![
AgentRechargeStatus.COMPLETED,
AgentRechargeStatus.REJECTED,
AgentRechargeStatus.CLOSED
AgentRechargeStatus.CLOSED,
AgentRechargeStatus.APPROVAL_SUBMISSION_FAILED
].includes(row.status)
if (
@@ -67,6 +70,16 @@ export const buildAgentRechargeActions = (
})
}
if (
row.status === AgentRechargeStatus.APPROVAL_SUBMISSION_FAILED &&
row.payment_method === 'offline' &&
options.canCreateOffline &&
options.onRestart &&
options.hasAuth('agent_recharge:create')
) {
actions.push({ label: '重新发起', handler: () => options.onRestart?.(row), type: 'primary' })
}
if (
!hasApprovalRecord &&
row.status === AgentRechargeStatus.PENDING &&

View File

@@ -72,7 +72,8 @@
3: 'success', // 已完成
4: 'info', // 已关闭
5: 'danger', // 已退款
6: 'danger' // 已驳回
6: 'danger', // 已驳回
7: 'danger' // 审批提交失败
}
return statusMap[status] || 'info'
}
@@ -131,7 +132,11 @@
{
label: '状态',
render: (data) =>
h(ElTag, { type: getStatusType(data.status) }, () => data.status_name || '-')
h(
ElTag,
{ type: getStatusType(data.status) },
() => data.status_name || (data.status === 7 ? '审批提交失败' : '-')
)
},
...(!isRestrictedCustomerRole.value
? [

View File

@@ -359,6 +359,7 @@
</template>
<script setup lang="ts">
import { AgentRechargeStatus as AgentRechargeStatusValue } from '@/types/api/agentRecharge'
import { h, watch } from 'vue'
import { useRouter } from 'vue-router'
import QrcodeVue from 'qrcode.vue'
@@ -568,7 +569,8 @@
{ label: '已完成', value: 3 },
{ label: '已关闭', value: 4 },
{ label: '已退款', value: 5 },
{ label: '已驳回', value: 6 }
{ label: '已驳回', value: 6 },
{ label: '审批提交失败', value: AgentRechargeStatusValue.APPROVAL_SUBMISSION_FAILED }
],
config: { clearable: true }
},
@@ -781,7 +783,8 @@
3: 'success', // 已完成
4: 'info', // 已关闭
5: 'danger', // 已退款
6: 'danger' // 已驳回
6: 'danger', // 已驳回
7: 'danger' // 审批提交失败
}
return statusMap[status] || 'info'
}
@@ -848,7 +851,15 @@
label: '状态',
width: 100,
formatter: (row: AgentRecharge) => {
return h(ElTag, { type: getStatusType(row.status) }, () => row.status_name || '-')
return h(
ElTag,
{ type: getStatusType(row.status) },
() =>
row.status_name ||
(row.status === AgentRechargeStatusValue.APPROVAL_SUBMISSION_FAILED
? '审批提交失败'
: '-')
)
}
},
{
@@ -1586,7 +1597,12 @@
onViewPaymentVoucher: handleViewPaymentVoucher,
onConfirmPayment: handleShowConfirmPay,
onReject: handleShowReject,
onTriggerApproval: handleTriggerApproval
onTriggerApproval: handleTriggerApproval,
canCreateOffline: isPlatformAccount.value,
onRestart: async () => {
if (!isPlatformAccount.value || !hasAuth('agent_recharge:create')) return
await showCreateDialog()
}
})
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.agentRechargeEntry)) {
const resourceTarget = resolveAuditResourceTarget({

View File

@@ -11,7 +11,17 @@
<h2 class="detail-title">核销申请详情</h2>
<ElButton
v-if="
application?.recoverable &&
application &&
canResubmitApplication(application) &&
hasAuth(AUGUST_PERMISSIONS.employeeCollection.applicationUpdate)
"
type="primary"
@click="applicationDialogVisible = true"
>修改并重新提交</ElButton
>
<ElButton
v-if="
canRecoverApplicationApproval(application) &&
hasAuth(AUGUST_PERMISSIONS.employeeCollection.applicationRecoverApproval)
"
type="warning"
@@ -23,6 +33,11 @@
</div>
<DetailPage v-if="application" :sections="detailSections" :data="application" />
<ApplicationFormDialog
v-model="applicationDialogVisible"
:application="application"
@success="loadDetail"
/>
<div v-if="loading" class="loading-container">
<ElIcon class="is-loading"><Loading /></ElIcon>
@@ -116,7 +131,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, h, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
ElButton,
@@ -124,6 +139,7 @@
ElIcon,
ElMessage,
ElMessageBox,
ElTag,
ElTable,
ElTableColumn,
ElTimeline,
@@ -145,9 +161,14 @@
import { getErrorMessage, toVoucherKeyList } from '@/utils/business'
import { formatDateTime } from '@/utils/business/format'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
import ApplicationFormDialog from './components/ApplicationFormDialog.vue'
import {
formatCollectionCurrency,
canRecoverApplicationApproval,
canResubmitApplication,
getApplicationSubmissionLabel,
getApplicationStatusLabel,
getApplicationStatusTagType,
getBillStatusLabel
} from '../employeeCollectionDisplay'
@@ -159,6 +180,7 @@
const loading = ref(false)
const recovering = ref(false)
const applicationDialogVisible = ref(false)
const detail = ref<EmployeeCollectionApplicationDetailData | null>(null)
const application = computed<EmployeeCollectionApplication | null>(
@@ -196,12 +218,16 @@
},
{
label: '状态',
prop: 'status',
formatter: (value, data) => data.status_name || getApplicationStatusLabel(value)
render: (data: EmployeeCollectionApplication) =>
h(
ElTag,
{ type: getApplicationStatusTagType(data.status) },
() => data.status_name || getApplicationStatusLabel(data.status)
)
},
{
label: '审批提交状态',
formatter: (_, data) => data.approval_submission_name || '-'
formatter: (_, data) => getApplicationSubmissionLabel(data)
},
{
label: '提交失败摘要',
@@ -270,7 +296,8 @@
const recoverApproval = async () => {
if (
!application.value?.recoverable ||
!application.value ||
!canRecoverApplicationApproval(application.value) ||
!hasAuth(AUGUST_PERMISSIONS.employeeCollection.applicationRecoverApproval) ||
recovering.value
) {

View File

@@ -76,6 +76,8 @@
import {
APPLICATION_STATUS_OPTIONS,
canResubmitApplication,
canRecoverApplicationApproval,
getApplicationSubmissionLabel,
formatCollectionCurrency,
getApplicationStatusLabel,
getApplicationStatusTagType,
@@ -191,15 +193,15 @@
prop: 'approval_submission_name',
label: '提交状态',
width: 130,
formatter: (row: EmployeeCollectionApplication) => row.approval_submission_name || '-'
formatter: getApplicationSubmissionLabel
},
{
prop: 'recoverable',
label: '可恢复',
width: 90,
formatter: (row: EmployeeCollectionApplication) =>
h(ElTag, { type: row.recoverable ? 'warning' : 'info' }, () =>
row.recoverable ? '是' : '否'
h(ElTag, { type: canRecoverApplicationApproval(row) ? 'warning' : 'info' }, () =>
canRecoverApplicationApproval(row) ? '是' : '否'
)
},
{
@@ -291,7 +293,7 @@
const handleRecoverApproval = async (row: EmployeeCollectionApplication) => {
if (
!row.recoverable ||
!canRecoverApplicationApproval(row) ||
!hasAuth(AUGUST_PERMISSIONS.employeeCollection.applicationRecoverApproval)
) {
return
@@ -322,7 +324,7 @@
actions.push({ label: '修改并重新提交', handler: () => handleResubmit(row), type: 'primary' })
}
if (
row.recoverable &&
canRecoverApplicationApproval(row) &&
hasAuth(AUGUST_PERMISSIONS.employeeCollection.applicationRecoverApproval)
) {
actions.push({

View File

@@ -24,7 +24,8 @@ export const APPLICATION_STATUS = {
PENDING: 0,
APPROVED: 1,
REJECTED: 2,
REVOKED: 3
REVOKED: 3,
APPROVAL_SUBMISSION_FAILED: 4
} as const
/**
@@ -77,7 +78,8 @@ export const APPLICATION_STATUS_OPTIONS = [
{ label: '审批中', value: APPLICATION_STATUS.PENDING },
{ label: '已通过', value: APPLICATION_STATUS.APPROVED },
{ label: '已驳回', value: APPLICATION_STATUS.REJECTED },
{ label: '已关闭', value: APPLICATION_STATUS.REVOKED }
{ label: '已关闭', value: APPLICATION_STATUS.REVOKED },
{ label: '审批提交失败', value: APPLICATION_STATUS.APPROVAL_SUBMISSION_FAILED }
]
/**
@@ -154,6 +156,8 @@ export const getApplicationStatusTagType = (
status?: EmployeeCollectionApplicationStatus | null
): TagType => {
switch (status) {
case APPLICATION_STATUS.APPROVAL_SUBMISSION_FAILED:
return 'danger'
case APPLICATION_STATUS.PENDING:
return 'warning'
case APPLICATION_STATUS.APPROVED:
@@ -211,5 +215,32 @@ export const canCreateApplication = (bill: EmployeeCollectionBill): boolean => {
* 申请是否可修改并重新提交
*/
export const canResubmitApplication = (application: EmployeeCollectionApplication): boolean => {
return application.status === APPLICATION_STATUS.REJECTED
return (
application.status === APPLICATION_STATUS.REJECTED ||
application.status === APPLICATION_STATUS.APPROVAL_SUBMISSION_FAILED
)
}
export const canRecoverApplicationApproval = (
application?: EmployeeCollectionApplication | null
): boolean =>
!!application &&
application.status !== APPLICATION_STATUS.APPROVAL_SUBMISSION_FAILED &&
application.recoverable === true
export const getApplicationSubmissionLabel = (
application: EmployeeCollectionApplication
): string => {
const labels: Record<number, string> = {
0: '待提交',
1: '提交中',
2: '已提交',
3: '提交失败',
4: '结果未知'
}
const status = application.approval_submission_status
return (
application.approval_submission_name ||
(status == null ? '-' : (labels[status] ?? String(status)))
)
}

View File

@@ -185,7 +185,7 @@
<script setup lang="ts">
import { ref, onMounted, computed, h, reactive } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElButton, ElCard, ElIcon, ElMessage, ElMessageBox } from 'element-plus'
import { ElButton, ElCard, ElIcon, ElMessage, ElMessageBox, ElTag } from 'element-plus'
import type { FormRules } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
import DetailPage from '@/components/common/DetailPage.vue'
@@ -201,7 +201,10 @@
import {
RefundMethodOptions,
canManualApproveRefund,
canResubmitRefund
canResubmitRefund,
canRecoverRefundApproval,
RefundStatusNameMap,
RefundStatusTypeMap
} from '@/config/constants/refund'
import { fenToYuan, formatDateTime, yuanToFen } from '@/utils/business/format'
import { toVoucherKeyList, getErrorMessage } from '@/utils/business'
@@ -431,7 +434,15 @@
{ label: '订单号', prop: 'order_no' },
{ label: '资产标识符', prop: 'asset_identifier' },
{ label: '资产类型', prop: 'asset_type' },
{ label: '退款状态', prop: 'status_name', formatter: (value) => value || '-' },
{
label: '退款状态',
render: (data: Refund) =>
h(
ElTag,
{ type: RefundStatusTypeMap[data.status] || 'info' },
() => data.status_name || RefundStatusNameMap[data.status] || '-'
)
},
...(!isRestrictedCustomerRole.value
? [
{
@@ -735,7 +746,7 @@
return canResubmitRefund(item)
}
const canRecoverApproval = (item: Refund) => item.approval_recoverable === true
const canRecoverApproval = canRecoverRefundApproval
const isApprovalRecoveryConflict = (error: unknown, message = '') => {
if (!error || typeof error !== 'object') {

View File

@@ -207,6 +207,8 @@
RefundMethodOptions,
RefundStatusTypeMap,
canResubmitRefund,
canRecoverRefundApproval,
RefundStatusNameMap,
canManualApproveRefund
} from '@/config/constants/refund'
import type { SearchFormItem } from '@/types'
@@ -344,7 +346,8 @@
{ label: '已拒绝', value: 3 },
{ label: '已退回', value: 4 },
{ label: '原路退款处理中', value: 5 },
{ label: '原路退款失败', value: 6 }
{ label: '原路退款失败', value: 6 },
{ label: '审批提交失败', value: RefundStatus.APPROVAL_SUBMISSION_FAILED }
],
config: {
clearable: true
@@ -477,7 +480,7 @@
prop: 'refund_no',
label: '退款单号',
minWidth: 210,
fixed:true,
fixed: true,
formatter: (row: Refund) => {
return h(
'span',
@@ -608,7 +611,11 @@
label: '状态',
width: 100,
formatter: (row: Refund) => {
return h(ElTag, { type: getStatusType(row.status) }, () => row.status_name || '-')
return h(
ElTag,
{ type: getStatusType(row.status) },
() => row.status_name || RefundStatusNameMap[row.status] || '-'
)
}
},
{
@@ -953,7 +960,7 @@
return row.status === RefundStatus.PENDING && approvalStatusEmpty
}
const canRecoverApproval = (row: Refund) => row.approval_recoverable === true
const canRecoverApproval = canRecoverRefundApproval
const isApprovalRecoveryConflict = (error: unknown, message = '') => {
if (!error || typeof error !== 'object') {

View File

@@ -15,6 +15,52 @@
<!-- 详情内容 -->
<DetailPage v-if="detailData" :sections="detailSections" :data="detailData" />
<ElCard v-if="detailData" style="margin-top: 20px" shadow="never">
<div class="order-items-section">
<h3 class="section-title">支付记录</h3>
<ElTable :data="detailData.payments || []" border empty-text="暂无支付记录">
<ElTableColumn prop="payment_no" label="支付单号" min-width="200" />
<ElTableColumn label="支付方式" width="100">
<template #default="{ row }">{{ getPaymentMethodText(row.payment_method) }}</template>
</ElTableColumn>
<ElTableColumn label="支付渠道" min-width="140">
<template #default="{ row }">{{
getOrderPaymentChannelName(row.payment_channel)
}}</template>
</ElTableColumn>
<ElTableColumn label="收款商户" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ row.payment_merchant_name || '-' }}</template>
</ElTableColumn>
<ElTableColumn label="交易流水号" min-width="200" show-overflow-tooltip>
<template #default="{ row }">{{ row.third_party_trade_no || '-' }}</template>
</ElTableColumn>
<ElTableColumn label="金额(元)" width="120">
<template #default="{ row }">{{ formatCurrency(row.amount) }}</template>
</ElTableColumn>
<ElTableColumn label="支付单状态" width="120">
<template #default="{ row }">
<ElTag :type="getOrderPaymentRecordTagType(row.status)">
{{ getOrderPaymentRecordStatusName(row.status, row.status_name) }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" width="180">
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
</ElTableColumn>
<ElTableColumn label="支付时间" width="180">
<template #default="{ row }">{{
row.paid_at ? formatDateTime(row.paid_at) : '-'
}}</template>
</ElTableColumn>
<ElTableColumn label="过期时间" width="180">
<template #default="{ row }">{{
row.expire_at ? formatDateTime(row.expire_at) : '-'
}}</template>
</ElTableColumn>
</ElTable>
</div>
</ElCard>
<!-- 订单列表 -->
<div v-if="detailData && detailData.items && detailData.items.length > 0">
<ElCard style="margin-top: 20px" shadow="never">
@@ -66,13 +112,13 @@
<script setup lang="ts">
import { h, onMounted, ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElButton, ElCard, ElIcon, ElMessage, ElTable, ElTableColumn } from 'element-plus'
import { ElButton, ElCard, ElIcon, ElMessage, ElTable, ElTableColumn, ElTag } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
import DetailPage from '@/components/common/DetailPage.vue'
import type { DetailField, DetailSection } from '@/components/common/DetailPage.vue'
import { OrderService } from '@/api/modules'
import type {
Order,
OrderDetail,
OrderCommissionResult,
OrderCommissionStatus,
OrderOperatorType
@@ -81,6 +127,11 @@
import { useUserStore } from '@/store/modules/user'
import { formatDateTime } from '@/utils/business/format'
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
import {
getOrderPaymentChannelName,
getOrderPaymentRecordStatusName,
getOrderPaymentRecordTagType
} from '@/utils/business/orderPayment'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
defineOptions({ name: 'OrderDetail' })
@@ -91,7 +142,7 @@
const userStore = useUserStore()
const loading = ref(false)
const detailData = ref<Order | null>(null)
const detailData = ref<OrderDetail | null>(null)
const paymentVoucherFileKeys = ref<string[]>([])
// 格式化货币 - 将分转换为元
@@ -203,6 +254,9 @@
prop: 'payment_method',
formatter: (value) => getPaymentMethodText(value)
},
{ label: '交易流水号', prop: 'third_party_trade_no', formatter: (value) => value || '-' },
{ label: '支付渠道', prop: 'payment_channel', formatter: getOrderPaymentChannelName },
{ label: '收款商户', prop: 'payment_merchant_name', formatter: (value) => value || '-' },
{
label: '订单金额',
prop: 'total_amount',

View File

@@ -270,6 +270,7 @@
ShopService
} from '@/api/modules'
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
import { getOrderPaymentChannelName } from '@/utils/business/orderPayment'
import type { FormInstance, FormRules } from 'element-plus'
import type {
Order,
@@ -546,6 +547,9 @@
options.push(
{ label: '支付方式', prop: 'payment_method' },
{ label: '交易流水号', prop: 'third_party_trade_no' },
{ label: '支付渠道', prop: 'payment_channel' },
{ label: '收款商户', prop: 'payment_merchant_name' },
{ label: '支付时间', prop: 'paid_at' }
)
@@ -879,7 +883,7 @@
prop: 'order_no',
label: '订单编号',
minWidth: 220,
fixed:true,
fixed: true,
showOverflowTooltip: true,
formatter: (row: Order) => {
return h(
@@ -1072,6 +1076,26 @@
}
baseColumns.push(
{
prop: 'third_party_trade_no',
label: '交易流水号',
minWidth: 200,
showOverflowTooltip: true,
formatter: (row: Order) => row.third_party_trade_no || '-'
},
{
prop: 'payment_channel',
label: '支付渠道',
width: 140,
formatter: (row: Order) => getOrderPaymentChannelName(row.payment_channel)
},
{
prop: 'payment_merchant_name',
label: '收款商户',
minWidth: 180,
showOverflowTooltip: true,
formatter: (row: Order) => row.payment_merchant_name || '-'
},
{
prop: 'payment_method',
label: '支付方式',

View File

@@ -16,11 +16,7 @@
@refresh="handleRefresh"
>
<template #left>
<ElButton
v-if="canCreate"
type="primary"
@click="openFormDialog('create')"
>
<ElButton v-if="canCreate" type="primary" @click="openFormDialog('create')">
新增规则
</ElButton>
</template>
@@ -121,7 +117,6 @@
<script setup lang="ts">
import { computed, h, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox, ElTag, FormInstance, FormRules } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { PackageManageService, PackageTrafficAlertService } from '@/api/modules'
import { useAuth } from '@/composables/useAuth'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
@@ -218,12 +213,6 @@
minWidth: 180,
showOverflowTooltip: true
},
{
prop: 'real_data_mb',
label: '当前真流量额度',
width: 150,
formatter: (row: PackageTrafficAlertRuleItem) => formatMbToGb(row.real_data_mb)
},
{
prop: 'threshold_percent',
label: '预警阈值',
@@ -261,7 +250,6 @@
const columnOptions = [
{ label: '套餐名称', prop: 'package_name' },
{ label: '当前真流量额度', prop: 'real_data_mb' },
{ label: '预警阈值', prop: 'threshold_percent' },
{ label: '状态', prop: 'enabled' },
{ label: '备注', prop: 'remark' },

View File

@@ -49,6 +49,7 @@
import type { DetailSection } from '@/components/common/DetailPage.vue'
import { formatDateTime } from '@/utils/business/format'
import { normalizeApiError } from '@/utils/business/apiError'
import { formatTrafficAlertMb } from '@/utils/business/trafficAlert'
import {
getTrafficAlertAssetTypeName,
getTrafficAlertNotificationStatusOption
@@ -78,6 +79,9 @@
title: '触发信息',
fields: [
{ label: '套餐', prop: 'package_name' },
{ label: '阈值流量(MB)', prop: 'threshold_mb', formatter: formatTrafficAlertMb },
{ label: '已用流量(MB,含加油包)', prop: 'used_mb', formatter: formatTrafficAlertMb },
{ label: '总流量(MB,含加油包)', prop: 'limit_mb', formatter: formatTrafficAlertMb },
{
label: '触发阈值',
prop: 'threshold_percent',

View File

@@ -72,6 +72,7 @@
</template>
<script setup lang="ts">
import { formatTrafficAlertMb } from '@/utils/business/trafficAlert'
import { computed, h, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElButton, ElMessage, ElMessageBox, ElRadio, ElRadioGroup, ElTag } from 'element-plus'
@@ -80,7 +81,6 @@
import { useAuth } from '@/composables/useAuth'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { formatDateTime } from '@/utils/business/format'
import { normalizeApiError } from '@/utils/business/apiError'
import {
AUGUST_PERMISSIONS,
TRAFFIC_ALERT_ASSET_TYPE_OPTIONS,
@@ -285,10 +285,8 @@
formatter: (row: PackageTrafficAlertRecordItem) => {
const packageName = row.package_name || '-'
if (!canViewDetail.value) return packageName
return h(
ElButton,
{ link: true, type: 'primary', onClick: () => goDetail(row) },
() => h('span', { style: { textDecoration: 'underline' } }, packageName)
return h(ElButton, { link: true, type: 'primary', onClick: () => goDetail(row) }, () =>
h('span', { style: { textDecoration: 'underline' } }, packageName)
)
}
},
@@ -343,6 +341,24 @@
width: 180,
formatter: (row: PackageTrafficAlertRecordItem) => formatDateTime(row.triggered_at)
},
{
prop: 'threshold_mb',
label: '阈值流量(MB)',
width: 150,
formatter: (row: PackageTrafficAlertRecordItem) => formatTrafficAlertMb(row.threshold_mb)
},
{
prop: 'used_mb',
label: '已用流量(MB,含加油包)',
minWidth: 210,
formatter: (row: PackageTrafficAlertRecordItem) => formatTrafficAlertMb(row.used_mb)
},
{
prop: 'limit_mb',
label: '总流量(MB,含加油包)',
minWidth: 200,
formatter: (row: PackageTrafficAlertRecordItem) => formatTrafficAlertMb(row.limit_mb)
},
{
prop: 'notification_status',
label: '通知状态',
@@ -366,6 +382,9 @@
{ label: '资产类型', prop: 'asset_type' },
{ label: '资产标识', prop: 'asset_identifier' },
{ label: '触发阈值', prop: 'threshold_percent' },
{ label: '阈值流量(MB)', prop: 'threshold_mb' },
{ label: '已用流量(MB,含加油包)', prop: 'used_mb' },
{ label: '总流量(MB,含加油包)', prop: 'limit_mb' },
{ label: '触发时间', prop: 'triggered_at' },
{ label: '通知状态', prop: 'notification_status' }
]

View File

@@ -6,7 +6,7 @@
<h2>代理注册申请详情</h2>
<ElButton
v-if="
application?.recoverable &&
canRecoverAgentRegistration(application) &&
hasAuth(AUGUST_PERMISSIONS.agentDistributionRegistration.recoverApproval)
"
type="warning"
@@ -17,11 +17,25 @@
</ElButton>
</div>
<ElAlert
v-if="application?.status === 3"
title="审批提交失败,请申请人重新扫码申请"
type="error"
:closable="false"
show-icon
style="margin-bottom: 16px"
/>
<ElDescriptions v-if="application" :column="2" border>
<ElDescriptionsItem label="申请ID">{{ application.id }}</ElDescriptionsItem>
<ElDescriptionsItem label="业务状态">{{
application.status_name || '-'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="业务状态"
><ElTag
:type="
application.status === 1 ? 'success' : application.status === 0 ? 'warning' : 'danger'
"
>{{ getAgentRegistrationStatusName(application) }}</ElTag
></ElDescriptionsItem
>
<ElDescriptionsItem label="审批状态">{{
application.approval_status_name || '-'
}}</ElDescriptionsItem>
@@ -79,6 +93,10 @@
import { useAuth } from '@/composables/useAuth'
import { formatDateTime } from '@/utils/business/format'
import type { AgentDistributionRegistrationItem } from '@/types/api'
import {
canRecoverAgentRegistration,
getAgentRegistrationStatusName
} from '@/types/api/agentDistributionRegistration'
defineOptions({ name: 'AgentDistributionRegistrationDetail' })
@@ -102,7 +120,12 @@
}
const recoverApproval = async () => {
if (!application.value?.recoverable || recovering.value) return
if (
!canRecoverAgentRegistration(application.value) ||
!hasAuth(AUGUST_PERMISSIONS.agentDistributionRegistration.recoverApproval) ||
recovering.value
)
return
try {
await ElMessageBox.confirm('确定要恢复该代理注册申请的原审批提交吗?', '恢复审批', {
type: 'warning',

View File

@@ -36,6 +36,10 @@
</template>
<script setup lang="ts">
import {
canRecoverAgentRegistration,
getAgentRegistrationStatusName
} from '@/types/api/agentDistributionRegistration'
import { h, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElTag } from 'element-plus'
@@ -74,7 +78,8 @@
options: [
{ label: '待审批', value: 0 },
{ label: '已通过', value: 1 },
{ label: '已驳回', value: 2 }
{ label: '已驳回', value: 2 },
{ label: '审批提交失败', value: 3 }
],
config: { clearable: true, placeholder: '请选择业务状态' }
},
@@ -129,8 +134,11 @@
const formatStatus = (row: AgentDistributionRegistrationItem) =>
h(
ElTag,
{ type: row.status === 1 ? 'success' : row.status === 2 ? 'danger' : 'warning' },
() => row.status_name || '-'
{
type:
row.status === 1 ? 'success' : row.status === 2 || row.status === 3 ? 'danger' : 'warning'
},
() => getAgentRegistrationStatusName(row)
)
const { columnChecks, columns } = useCheckedColumns(() => [
@@ -175,8 +183,8 @@
label: '可恢复',
width: 90,
formatter: (row: AgentDistributionRegistrationItem) =>
h(ElTag, { type: row.recoverable ? 'warning' : 'info' }, () =>
row.recoverable ? '是' : '否'
h(ElTag, { type: canRecoverAgentRegistration(row) ? 'warning' : 'info' }, () =>
canRecoverAgentRegistration(row) ? '是' : '否'
)
},
{

View File

@@ -60,9 +60,9 @@
<ElAlert type="info" :closable="false" show-icon class="import-alert">
<template #title>导入说明</template>
<div>通过 CSV 批量交接或清空店铺平台业务员。</div>
<div>注意: 清空不要填写业务员登录账号</div>
<div>平台登录账号只填写平台用户的登录手机号,不支持用户名;清空时该列留空。</div>
<div>
表头固定:<code>店铺编码,操作类型,业务员登录账号,备注</code>;操作类型仅支持「换绑」或「清空」。
表头固定:<code>店铺编码,操作类型,平台登录账号,备注</code>;操作类型仅支持「换绑」或「清空」,旧表头不再支持。
</div>
<div>支持 UTF-8(可带 BOM),一行失败不影响其他行。</div>
</ElAlert>
@@ -191,6 +191,7 @@
</template>
<script setup lang="ts">
import { SHOP_BUSINESS_OWNER_CSV_TEMPLATE } from '@/utils/business/shopBusinessOwnerImport'
import { computed, h, onMounted, onBeforeUnmount, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
@@ -249,7 +250,7 @@
prop: 'task_no',
label: '任务编号',
width: 160,
fixed:true,
fixed: true,
showOverflowTooltip: true,
formatter: (row: BusinessOwnerImportDetail) =>
h(
@@ -361,11 +362,15 @@
const handleDownloadTemplate = () => {
try {
const link = document.createElement('a')
link.href = new URL('@/template/批量换绑业务员模板.csv', import.meta.url).href
const templateUrl = URL.createObjectURL(
new Blob([SHOP_BUSINESS_OWNER_CSV_TEMPLATE], { type: 'text/csv;charset=utf-8' })
)
link.href = templateUrl
link.download = '批量换绑业务员模板.csv'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
setTimeout(() => URL.revokeObjectURL(templateUrl), 0)
ElMessage.success('模板下载成功')
} catch (error) {
console.error('下载模板失败:', error)

View File

@@ -189,7 +189,8 @@
<ElInput
v-model="formData.init_password"
type="password"
placeholder="请输入初始账号密码"
:placeholder="NEW_LOGIN_PASSWORD_HINT"
maxlength="12"
show-password
/>
</ElFormItem>
@@ -384,6 +385,10 @@
</template>
<script setup lang="ts">
import {
createNewLoginPasswordRule,
NEW_LOGIN_PASSWORD_HINT
} from '@/utils/validation/loginPassword'
import { h } from 'vue'
import { useRouter } from 'vue-router'
import {
@@ -1344,7 +1349,7 @@
],
init_password: [
{ required: true, message: '请输入初始账号密码', trigger: 'blur' },
{ min: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
createNewLoginPasswordRule()
],
init_phone: [
{ required: true, message: '请输入初始账号手机号', trigger: 'blur' },
@@ -1389,7 +1394,11 @@
if (formData.contact_name) data.contact_name = formData.contact_name
if (formData.contact_phone) data.contact_phone = formData.contact_phone
await ShopService.createShop(data)
const res = await ShopService.createShop(data)
if (res.code !== 0) {
ElMessage.error(res.msg || res.message || '店铺创建失败')
return
}
ElMessage.success('新增成功')
} else {
const data: UpdateShopParams = {
@@ -1423,8 +1432,8 @@
await getShopList()
// 提交成功后也重新加载顶级店铺
await loadTopLevelShops()
} catch (error) {
console.error(error)
} catch {
// HTTP 层已提示,不输出带初始密码的请求错误对象。
} finally {
submitLoading.value = false
}