修改bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m54s

This commit is contained in:
sexygoat
2026-04-09 13:48:11 +08:00
parent cc9d2a85a2
commit cb3ba06106
26 changed files with 987 additions and 1984 deletions

View File

@@ -3,6 +3,7 @@
*/ */
import { BaseService } from '../BaseService' import { BaseService } from '../BaseService'
import request from '@/utils/http'
import type { import type {
PlatformRole, PlatformRole,
RoleQueryParams, RoleQueryParams,
@@ -93,12 +94,16 @@ export class RoleService extends BaseService {
} }
/** /**
* 移除角色的单个权限 * 移除角色的权限(支持批量)
* DELETE /api/admin/roles/{role_id}/permissions/{perm_id} * DELETE /api/admin/roles/{role_id}/permissions
* @param roleId 角色ID * @param roleId 角色ID
* @param permId 权限ID * @param permIds 权限ID列表
*/ */
static removePermission(roleId: number, permId: number): Promise<BaseResponse> { static removePermissions(roleId: number, permIds: number[]): Promise<BaseResponse> {
return this.delete<BaseResponse>(`/api/admin/roles/${roleId}/permissions/${permId}`) // 直接使用 request.del传递 data 到 Body
return request.del<BaseResponse>({
url: `/api/admin/roles/${roleId}/permissions`,
data: { perm_ids: permIds }
})
} }
} }

View File

@@ -24,6 +24,34 @@ export class ShopService extends BaseService {
return this.getPage<ShopResponse>('/api/admin/shops', params) return this.getPage<ShopResponse>('/api/admin/shops', params)
} }
/**
* 店铺级联查询(树结构)
* GET /api/admin/shops/cascade
* @param params 查询参数
*/
static getShopsCascade(params?: {
shop_name?: string
parent_id?: number
}): Promise<
BaseResponse<
Array<{
id: number
shop_name: string
has_children: boolean
}>
>
> {
return this.get<
BaseResponse<
Array<{
id: number
shop_name: string
has_children: boolean
}>
>
>('/api/admin/shops/cascade', params)
}
/** /**
* 创建店铺 * 创建店铺
* POST /api/admin/shops * POST /api/admin/shops

View File

@@ -79,26 +79,26 @@
<!-- </div>--> <!-- </div>-->
<!--</div>--> <!--</div>-->
<!-- 语言 --> <!-- 语言 -->
<div class="btn-box" v-if="showLanguage"> <!--<div class="btn-box" v-if="showLanguage">-->
<el-dropdown @command="changeLanguage" popper-class="langDropDownStyle"> <!-- <el-dropdown @command="changeLanguage" popper-class="langDropDownStyle">-->
<div class="btn language-btn"> <!-- <div class="btn language-btn">-->
<i class="iconfont-sys">&#xe611;</i> <!-- <i class="iconfont-sys">&#xe611;</i>-->
</div> <!-- </div>-->
<template #dropdown> <!-- <template #dropdown>-->
<el-dropdown-menu> <!-- <el-dropdown-menu>-->
<div v-for="item in languageOptions" :key="item.value" class="lang-btn-item"> <!-- <div v-for="item in languageOptions" :key="item.value" class="lang-btn-item">-->
<el-dropdown-item <!-- <el-dropdown-item-->
:command="item.value" <!-- :command="item.value"-->
:class="{ 'is-selected': locale === item.value }" <!-- :class="{ 'is-selected': locale === item.value }"-->
> <!-- >-->
<span class="menu-txt">{{ item.label }}</span> <!-- <span class="menu-txt">{{ item.label }}</span>-->
<i v-if="locale === item.value" class="iconfont-sys">&#xe621;</i> <!-- <i v-if="locale === item.value" class="iconfont-sys">&#xe621;</i>-->
</el-dropdown-item> <!-- </el-dropdown-item>-->
</div> <!-- </div>-->
</el-dropdown-menu> <!-- </el-dropdown-menu>-->
</template> <!-- </template>-->
</el-dropdown> <!-- </el-dropdown>-->
</div> <!--</div>-->
<!-- 设置 --> <!-- 设置 -->
<div class="btn-box" @click="openSetting"> <div class="btn-box" @click="openSetting">
<el-popover :visible="showSettingGuide" placement="bottom-start" :width="190" :offset="0"> <el-popover :visible="showSettingGuide" placement="bottom-start" :width="190" :offset="0">
@@ -162,7 +162,7 @@
<i class="menu-icon iconfont-sys">&#xe817;</i> <i class="menu-icon iconfont-sys">&#xe817;</i>
<span class="menu-txt">{{ $t('topBar.user.lockScreen') }}</span> <span class="menu-txt">{{ $t('topBar.user.lockScreen') }}</span>
</li> </li>
<div class="line"></div> <div class="line" />
<div class="logout-btn" @click="loginOut"> <div class="logout-btn" @click="loginOut">
{{ $t('topBar.user.logout') }} {{ $t('topBar.user.logout') }}
</div> </div>
@@ -180,7 +180,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { LanguageEnum, MenuTypeEnum, MenuWidth } from '@/enums/appEnum' import { MenuTypeEnum, MenuWidth } from '@/enums/appEnum'
import { useSettingStore } from '@/store/modules/setting' import { useSettingStore } from '@/store/modules/setting'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core'
@@ -190,7 +190,6 @@
import { mittBus } from '@/utils/sys' import { mittBus } from '@/utils/sys'
import { useMenuStore } from '@/store/modules/menu' import { useMenuStore } from '@/store/modules/menu'
import AppConfig from '@/config' import AppConfig from '@/config'
import { languageOptions } from '@/locales'
const isWindows = navigator.userAgent.includes('Windows') const isWindows = navigator.userAgent.includes('Windows')
const { locale } = useI18n() const { locale } = useI18n()
@@ -201,7 +200,6 @@
const { const {
showMenuButton, showMenuButton,
showRefreshButton, showRefreshButton,
showLanguage,
menuOpen, menuOpen,
showCrumbs, showCrumbs,
systemThemeColor, systemThemeColor,
@@ -225,7 +223,6 @@
const isTopLeftMenu = computed(() => menuType.value === MenuTypeEnum.TOP_LEFT) const isTopLeftMenu = computed(() => menuType.value === MenuTypeEnum.TOP_LEFT)
import { useCommon } from '@/composables/useCommon' import { useCommon } from '@/composables/useCommon'
import { WEB_LINKS } from '@/utils/constants'
import { themeAnimation } from '@/utils/theme/animation' import { themeAnimation } from '@/utils/theme/animation'
const { t } = useI18n() const { t } = useI18n()
@@ -322,13 +319,6 @@
locale.value = language.value locale.value = language.value
} }
const changeLanguage = (lang: LanguageEnum) => {
if (locale.value === lang) return
locale.value = lang
userStore.setLanguage(lang)
reload(50)
}
const openSetting = () => { const openSetting = () => {
mittBus.emit('openSetting') mittBus.emit('openSetting')
@@ -358,10 +348,6 @@
} }
} }
const visibleNotice = () => {
showNotice.value = !showNotice.value
}
const openChat = () => { const openChat = () => {
mittBus.emit('openChat') mittBus.emit('openChat')
} }

View File

@@ -404,16 +404,9 @@
"accountManagement": { "accountManagement": {
"title": "Account Management", "title": "Account Management",
"account": "Account List", "account": "Account List",
"platformAccount": "Platform Account",
"shopAccount": "Shop Account",
"customer": "Customer Management",
"customerRole": "Customer Role",
"agent": "Agent Management",
"customerAccount": "Customer Account",
"enterpriseCustomer": "Enterprise Customer", "enterpriseCustomer": "Enterprise Customer",
"enterpriseCustomerAccounts": "Enterprise Customer Accounts", "enterpriseCustomerAccounts": "Enterprise Customer Accounts",
"enterpriseCards": "Enterprise Card Management", "enterpriseCards": "Enterprise Card Management"
"customerCommission": "Customer Commission"
}, },
"orderManagement": { "orderManagement": {
"title": "Order Management", "title": "Order Management",
@@ -503,463 +496,5 @@
"zebra": "Zebra", "zebra": "Zebra",
"border": "Border", "border": "Border",
"headerBackground": "Header BG" "headerBackground": "Header BG"
},
"platformAccount": {
"title": "Platform Account Management",
"searchForm": {
"accountName": "Account Name",
"accountNamePlaceholder": "Please enter account name",
"username": "Username",
"usernamePlaceholder": "Please enter username",
"status": "Status",
"statusPlaceholder": "Please select status"
},
"table": {
"id": "Account ID",
"accountName": "Account Name",
"username": "Username",
"phone": "Phone",
"email": "Email",
"roles": "Roles",
"status": "Status",
"createTime": "Create Time",
"lastLoginTime": "Last Login",
"actions": "Actions"
},
"status": {
"enabled": "Enabled",
"disabled": "Disabled"
},
"buttons": {
"add": "Add Account",
"edit": "Edit",
"delete": "Delete",
"view": "View Details",
"changePassword": "Change Password",
"assignRoles": "Assign Roles"
},
"dialog": {
"add": "Add Platform Account",
"edit": "Edit Platform Account",
"detail": "Account Details",
"changePassword": "Change Password",
"assignRoles": "Assign Roles"
},
"form": {
"accountName": "Account Name",
"accountNamePlaceholder": "Please enter account name",
"username": "Username",
"usernamePlaceholder": "Please enter username (login name)",
"password": "Password",
"passwordPlaceholder": "Please enter password",
"confirmPassword": "Confirm Password",
"confirmPasswordPlaceholder": "Please enter password again",
"phone": "Phone",
"phonePlaceholder": "Please enter phone number",
"email": "Email",
"emailPlaceholder": "Please enter email",
"status": "Status",
"remark": "Remark",
"remarkPlaceholder": "Please enter remark",
"newPassword": "New Password",
"newPasswordPlaceholder": "Please enter new password",
"confirmNewPassword": "Confirm New Password",
"confirmNewPasswordPlaceholder": "Please enter new password again",
"currentRoles": "Current Roles",
"availableRoles": "Available Roles",
"basicInfo": "Basic Information",
"roleInfo": "Role Information",
"statusInfo": "Status Information",
"remarkInfo": "Remark Information",
"createTime": "Create Time",
"updateTime": "Update Time",
"lastLoginTime": "Last Login Time"
},
"validation": {
"accountNameRequired": "Please enter account name",
"usernameRequired": "Please enter username",
"usernameExists": "Username already exists",
"passwordRequired": "Please enter password",
"passwordStrength": "Password must be at least 8 characters with letters and numbers",
"confirmPasswordRequired": "Please enter password again",
"passwordNotMatch": "Passwords do not match",
"phoneRequired": "Please enter phone number",
"phonePattern": "Please enter valid phone number",
"emailPattern": "Please enter valid email",
"newPasswordRequired": "Please enter new password",
"atLeastOneRole": "Account must have at least one role"
},
"messages": {
"createSuccess": "Account created successfully",
"updateSuccess": "Account updated successfully",
"deleteSuccess": "Account deleted successfully",
"passwordChangeSuccess": "Password changed successfully",
"roleAssignSuccess": "Roles assigned successfully",
"statusChangeSuccess": "Status changed successfully",
"deleteConfirm": "Are you sure to delete account {name}? This action cannot be undone",
"cannotDeleteSelf": "Cannot delete current login account",
"cannotDisableSelf": "Cannot disable current login account",
"noData": "No account data, click add button to create account",
"passwordHint": "Please use change password function to modify password",
"disabledLoginHint": "Account has been disabled, please contact administrator"
}
},
"commission": {
"menu": {
"management": "Commission Management",
"withdrawal": "Withdrawal Approval",
"withdrawalSettings": "Withdrawal Settings",
"myCommission": "My Commission",
"agentCommission": "Agent Commission Management"
},
"table": {
"withdrawalNo": "Withdrawal No.",
"applicant": "Applicant",
"applicantAccount": "Applicant Account",
"withdrawalAmount": "Withdrawal Amount",
"fee": "Fee",
"actualAmount": "Actual Amount",
"withdrawalMethod": "Withdrawal Method",
"accountInfo": "Account Info",
"accountName": "Account Name",
"accountNumber": "Account Number",
"bankName": "Bank Name",
"status": "Status",
"rejectReason": "Reject Reason",
"applyTime": "Apply Time",
"approveTime": "Approve Time",
"transferTime": "Transfer Time",
"actions": "Actions",
"commissionNo": "Commission No.",
"commissionType": "Commission Type",
"commissionAmount": "Commission Amount",
"orderNo": "Order No.",
"sourceUser": "Source User",
"beneficiary": "Beneficiary",
"settleTime": "Settle Time",
"withdrawTime": "Withdraw Time",
"remark": "Remark"
},
"status": {
"pending": "Pending",
"approved": "Approved",
"rejected": "Rejected",
"completed": "Completed",
"frozen": "Frozen",
"unfreezing": "Unfreezing",
"settled": "Settled",
"invalid": "Invalid"
},
"type": {
"oneTime": "One-time Commission",
"longTerm": "Long-term Commission"
},
"method": {
"alipay": "Alipay",
"wechat": "WeChat",
"bank": "Bank Card"
},
"form": {
"withdrawalAmount": "Withdrawal Amount",
"withdrawalAmountPlaceholder": "Please enter withdrawal amount",
"withdrawalMethod": "Withdrawal Method",
"withdrawalMethodPlaceholder": "Please select withdrawal method",
"accountName": "Account Name",
"accountNamePlaceholder": "Please enter account name",
"accountNumber": "Account Number",
"accountNumberPlaceholder": "Please enter account number",
"bankName": "Bank Name",
"bankNamePlaceholder": "Please enter bank name",
"alipayAccount": "Alipay Account",
"alipayAccountPlaceholder": "Please enter Alipay account",
"wechatAccount": "WeChat Account",
"wechatAccountPlaceholder": "Please enter WeChat account",
"rejectReason": "Reject Reason",
"rejectReasonPlaceholder": "Please enter reject reason",
"remark": "Remark",
"remarkPlaceholder": "Please enter remark",
"minWithdrawal": "Min Withdrawal",
"minWithdrawalPlaceholder": "Please enter min withdrawal amount",
"maxWithdrawal": "Max Withdrawal",
"maxWithdrawalPlaceholder": "Please enter max withdrawal amount",
"feeRate": "Fee Rate",
"feeRatePlaceholder": "Please enter fee rate (%)",
"availableBalance": "Available Balance",
"totalCommission": "Total Commission",
"frozenAmount": "Frozen Amount",
"withdrawnAmount": "Withdrawn Amount"
},
"buttons": {
"approve": "Approve",
"reject": "Reject",
"applyWithdrawal": "Apply Withdrawal",
"viewDetail": "View Details",
"export": "Export",
"batchApprove": "Batch Approve",
"batchReject": "Batch Reject"
},
"messages": {
"approveSuccess": "Approved successfully",
"rejectSuccess": "Rejected successfully",
"withdrawalSuccess": "Withdrawal application submitted successfully",
"updateSuccess": "Updated successfully",
"deleteSuccess": "Deleted successfully",
"approveConfirm": "Are you sure to approve this withdrawal?",
"rejectConfirm": "Are you sure to reject this withdrawal?",
"batchApproveConfirm": "Are you sure to approve {count} withdrawals?",
"batchRejectConfirm": "Are you sure to reject {count} withdrawals?",
"insufficientBalance": "Insufficient balance",
"belowMinAmount": "Amount below minimum withdrawal",
"exceedMaxAmount": "Amount exceeds maximum withdrawal",
"noData": "No data"
},
"validation": {
"withdrawalAmountRequired": "Please enter withdrawal amount",
"withdrawalAmountInvalid": "Withdrawal amount must be greater than 0",
"withdrawalMethodRequired": "Please select withdrawal method",
"accountNameRequired": "Please enter account name",
"accountNumberRequired": "Please enter account number",
"bankNameRequired": "Please enter bank name",
"rejectReasonRequired": "Please enter reject reason",
"remarkRequired": "Please enter remark",
"minWithdrawalRequired": "Please enter min withdrawal amount",
"maxWithdrawalRequired": "Please enter max withdrawal amount",
"feeRateRequired": "Please enter fee rate",
"feeRateInvalid": "Fee rate must be between 0-100"
},
"searchForm": {
"withdrawalNo": "Withdrawal No.",
"withdrawalNoPlaceholder": "Please enter withdrawal no.",
"applicant": "Applicant",
"applicantPlaceholder": "Please enter applicant",
"shopName": "Shop Name",
"shopNamePlaceholder": "Please enter shop name",
"status": "Status",
"statusPlaceholder": "Please select status",
"withdrawalMethod": "Withdrawal Method",
"withdrawalMethodPlaceholder": "Please select withdrawal method",
"dateRange": "Date Range",
"dateRangePlaceholder": ["Start Date", "End Date"],
"commissionType": "Commission Type",
"commissionTypePlaceholder": "Please select commission type"
},
"dialog": {
"approve": "Approve Withdrawal",
"reject": "Reject Withdrawal",
"detail": "Withdrawal Details",
"applyWithdrawal": "Apply Withdrawal",
"withdrawalSettings": "Withdrawal Settings"
},
"summary": {
"title": "Commission Summary",
"totalCommission": "Total Commission",
"availableBalance": "Available Balance",
"frozenAmount": "Frozen Amount",
"withdrawnAmount": "Withdrawn Amount",
"pendingAmount": "Pending Amount",
"todayCommission": "Today's Commission"
}
},
"seriesBinding": {
"buttons": {
"batchSetSeries": "Batch Set Series",
"clearBinding": "Clear Binding"
},
"dialog": {
"title": "Batch Set Series Binding",
"titleCard": "Batch Set Card Series Binding",
"titleDevice": "Batch Set Device Series Binding"
},
"form": {
"seriesAllocation": "Series Allocation",
"seriesAllocationPlaceholder": "Please select series allocation",
"clearBindingOption": "Clear Association",
"selectedCount": "{count} items selected"
},
"messages": {
"noSelection": "Please select items to set",
"setSuccess": "Series binding set successfully",
"setFailed": "Failed to set series binding",
"partialSuccess": "Partial success: {success} succeeded, {fail} failed",
"confirmSet": "Are you sure to set series binding for {count} selected items?",
"confirmClear": "Are you sure to clear series binding for {count} selected items?"
},
"result": {
"title": "Set Result",
"successCount": "Success Count",
"failCount": "Fail Count",
"failedItems": "Failed Items",
"iccid": "ICCID",
"deviceId": "Device ID",
"deviceNo": "Device No.",
"reason": "Reason"
}
},
"orderManagement": {
"title": "Order Management",
"orderList": "Order List",
"orderDetail": "Order Details",
"orderItems": "Order Items",
"createOrder": "Create Order",
"searchForm": {
"orderNo": "Order No.",
"orderNoPlaceholder": "Please enter order number",
"paymentStatus": "Payment Status",
"paymentStatusPlaceholder": "Please select payment status",
"orderType": "Order Type",
"orderTypePlaceholder": "Please select order type",
"dateRange": "Created Time",
"startDate": "Start Time",
"endDate": "End Time"
},
"table": {
"id": "Order ID",
"orderNo": "Order No.",
"orderType": "Order Type",
"buyerId": "Buyer ID",
"buyerType": "Buyer Type",
"paymentStatus": "Payment Status",
"paymentMethod": "Payment Method",
"totalAmount": "Total Amount",
"paidAt": "Paid At",
"commissionStatus": "Commission Status",
"createdAt": "Created At",
"updatedAt": "Updated At",
"operation": "Actions"
},
"orderType": {
"singleCard": "Single Card Purchase",
"device": "Device Purchase"
},
"buyerType": {
"personal": "Personal Customer",
"agent": "Agent"
},
"paymentStatus": {
"pending": "Pending",
"paid": "Paid",
"cancelled": "Cancelled",
"refunded": "Refunded"
},
"paymentMethod": {
"wallet": "Wallet Payment",
"wechat": "WeChat Payment",
"alipay": "Alipay Payment",
"offline": "Offline Payment"
},
"commissionStatus": {
"notApplicable": "Not Applicable",
"pending": "Pending",
"settled": "Settled"
},
"actions": {
"viewDetail": "View Details",
"cancel": "Cancel",
"confirm": "Confirm",
"submit": "Submit",
"close": "Close"
},
"createForm": {
"packageIds": "Select Packages",
"packageIdsPlaceholder": "Please select packages",
"iotCardId": "IoT Card ID",
"iotCardIdPlaceholder": "Please enter IoT card ID",
"deviceId": "Device ID",
"deviceIdPlaceholder": "Please enter device ID"
},
"items": {
"packageName": "Package Name",
"quantity": "Quantity",
"unitPrice": "Unit Price",
"amount": "Subtotal"
},
"messages": {
"createSuccess": "Order created successfully",
"createFailed": "Failed to create order",
"cancelSuccess": "Order cancelled successfully",
"cancelFailed": "Failed to cancel order",
"cancelConfirm": "Cancel Order Confirmation",
"cancelConfirmText": "Are you sure to cancel this order? This action cannot be undone",
"cannotCancelPaid": "Cannot cancel a paid order",
"cannotCancelCancelled": "Order is already cancelled",
"cannotCancelRefunded": "Cannot cancel a refunded order",
"loadFailed": "Failed to load order data",
"noData": "No order data available"
},
"validation": {
"orderTypeRequired": "Please select order type",
"packageIdsRequired": "Please select at least one package",
"iotCardRequired": "Please select IoT card",
"deviceRequired": "Please select device",
"packagesMaxLimit": "Can select up to 10 packages"
}
},
"enterpriseDevices": {
"title": "Enterprise Device List",
"searchForm": {
"enterpriseId": "Enterprise ID",
"enterpriseIdPlaceholder": "Please enter enterprise ID",
"deviceNo": "Device No.",
"deviceNoPlaceholder": "Please enter device number"
},
"table": {
"deviceId": "Device ID",
"deviceNo": "Device No.",
"deviceName": "Device Name",
"deviceModel": "Device Model",
"cardCount": "Card Count",
"authorizedAt": "Authorized At",
"operation": "Actions"
},
"buttons": {
"allocateDevices": "Allocate Devices",
"recallDevices": "Recall Authorization",
"refresh": "Refresh"
},
"dialog": {
"allocateTitle": "Allocate Devices to Enterprise",
"recallTitle": "Recall Device Authorization",
"resultTitle": "Operation Result"
},
"form": {
"deviceNos": "Device Numbers",
"deviceNosPlaceholder": "Enter device numbers, separated by newlines or commas",
"deviceNosHint": "One device number per line or comma-separated, maximum 100 devices",
"remark": "Remark",
"remarkPlaceholder": "Enter allocation remark (optional)",
"selectedDevices": "Selected Devices",
"selectedCount": "{count} devices selected"
},
"result": {
"successCount": "Success Count",
"failCount": "Fail Count",
"authorizedDevices": "Authorized Devices",
"failedItems": "Failed Items",
"deviceNo": "Device No.",
"deviceId": "Device ID",
"cardCount": "Card Count",
"reason": "Failure Reason"
},
"messages": {
"allocateSuccess": "Device allocation successful",
"allocateFailed": "Device allocation failed",
"allocatePartialSuccess": "Partial success: {success} succeeded, {fail} failed",
"recallSuccess": "Authorization recall successful",
"recallFailed": "Authorization recall failed",
"recallPartialSuccess": "Partial recall success: {success} succeeded, {fail} failed",
"recallConfirm": "Recall Authorization Confirmation",
"recallConfirmText": "Are you sure to recall authorization for {count} selected devices?",
"noSelection": "Please select devices to recall first",
"deviceNosRequired": "Please enter device numbers",
"deviceNosEmpty": "Device number list cannot be empty",
"deviceNosMaxLimit": "Cannot exceed 100 device numbers",
"invalidDeviceNos": "Invalid device number format exists",
"loadFailed": "Failed to load device list",
"noData": "No device data available"
},
"validation": {
"deviceNosRequired": "Please enter device number list",
"deviceNosMaxLength": "Cannot exceed 100 device numbers"
}
} }
} }

View File

@@ -103,13 +103,13 @@
"subTitle": "美观实用的界面,经过视觉优化,确保卓越的用户体验" "subTitle": "美观实用的界面,经过视觉优化,确保卓越的用户体验"
}, },
"title": "欢迎回来", "title": "欢迎回来",
"subTitle": "输入您的号和密码登录", "subTitle": "输入您的手机号和密码登录",
"roles": { "roles": {
"super": "超级管理员", "super": "超级管理员",
"admin": "管理员", "admin": "管理员",
"user": "普通用户" "user": "普通用户"
}, },
"placeholder": ["请输入号", "请输入密码", "请拖动滑块完成验证"], "placeholder": ["请输入手机号", "请输入密码", "请拖动滑块完成验证"],
"sliderText": "按住滑块拖动", "sliderText": "按住滑块拖动",
"sliderSuccessText": "验证成功", "sliderSuccessText": "验证成功",
"rememberPwd": "记住密码", "rememberPwd": "记住密码",
@@ -122,7 +122,7 @@
"message": "欢迎回来" "message": "欢迎回来"
}, },
"validation": { "validation": {
"usernameLength": "用户名长度为 3-20 个字符", "usernameLength": "用户名长度为 2-20 个字符",
"usernamePattern": "用户名只能包含字母、数字和下划线", "usernamePattern": "用户名只能包含字母、数字和下划线",
"passwordLength": "密码长度为 6-20 个字符", "passwordLength": "密码长度为 6-20 个字符",
"strongPasswordLength": "密码长度为 8-20 个字符", "strongPasswordLength": "密码长度为 8-20 个字符",
@@ -354,16 +354,9 @@
"accountManagement": { "accountManagement": {
"title": "账号管理", "title": "账号管理",
"account": "账号列表", "account": "账号列表",
"platformAccount": "平台账号",
"shopAccount": "代理账号",
"customer": "客户管理",
"customerRole": "客户角色",
"agent": "代理商管理",
"customerAccount": "客户账号",
"enterpriseCustomer": "企业客户", "enterpriseCustomer": "企业客户",
"enterpriseCustomerAccounts": "关联账号列表", "enterpriseCustomerAccounts": "关联账号列表",
"enterpriseCards": "企业卡管理", "enterpriseCards": "企业卡管理"
"customerCommission": "客户账号佣金"
}, },
"shopManagement": { "shopManagement": {
"title": "店铺管理", "title": "店铺管理",
@@ -371,23 +364,15 @@
}, },
"assetManagement": { "assetManagement": {
"title": "资产管理", "title": "资产管理",
"cardSearch": "IoT卡查询",
"deviceSearch": "设备查询",
"assetInformation": "资产信息", "assetInformation": "资产信息",
"singleCard": "单卡信息",
"standaloneCardList": "IoT卡管理", "standaloneCardList": "IoT卡管理",
"iotCardDetail": "IoT卡详情",
"iotCardTask": "IoT卡任务", "iotCardTask": "IoT卡任务",
"deviceTask": "设备任务", "deviceTask": "设备任务",
"taskDetail": "任务详情", "taskDetail": "任务详情",
"devices": "设备管理", "devices": "设备管理",
"deviceDetail": "设备详情",
"assetAssign": "分配记录", "assetAssign": "分配记录",
"assetAssignDetail": "资产分配详情", "assetAssignDetail": "资产分配详情",
"allocationRecordDetail": "分配记录详情",
"cardReplacementRequest": "换卡申请",
"authorizationRecords": "授权记录", "authorizationRecords": "授权记录",
"authorizationDetail": "授权记录详情",
"authorizationRecordDetail": "授权记录详情", "authorizationRecordDetail": "授权记录详情",
"enterpriseDevices": "企业设备列表", "enterpriseDevices": "企业设备列表",
"recordsManagement": "记录管理", "recordsManagement": "记录管理",
@@ -438,463 +423,5 @@
"zebra": "斑马纹", "zebra": "斑马纹",
"border": "边框", "border": "边框",
"headerBackground": "表头背景" "headerBackground": "表头背景"
},
"platformAccount": {
"title": "平台账号管理",
"searchForm": {
"accountName": "账号名称",
"accountNamePlaceholder": "请输入账号名称",
"username": "用户名",
"usernamePlaceholder": "请输入用户名",
"status": "状态",
"statusPlaceholder": "请选择状态"
},
"table": {
"id": "账号ID",
"accountName": "账号名称",
"username": "用户名",
"phone": "手机号",
"email": "邮箱",
"roles": "角色",
"status": "状态",
"createTime": "创建时间",
"lastLoginTime": "最后登录时间",
"actions": "操作"
},
"status": {
"enabled": "启用",
"disabled": "禁用"
},
"buttons": {
"add": "新增账号",
"edit": "编辑",
"delete": "删除",
"view": "查看详情",
"changePassword": "修改密码",
"assignRoles": "分配角色"
},
"dialog": {
"add": "新增平台账号",
"edit": "编辑平台账号",
"detail": "账号详情",
"changePassword": "修改密码",
"assignRoles": "分配角色"
},
"form": {
"accountName": "账号名称",
"accountNamePlaceholder": "请输入账号名称",
"username": "用户名",
"usernamePlaceholder": "请输入用户名(登录名)",
"password": "密码",
"passwordPlaceholder": "请输入密码",
"confirmPassword": "确认密码",
"confirmPasswordPlaceholder": "请再次输入密码",
"phone": "手机号",
"phonePlaceholder": "请输入手机号",
"email": "邮箱",
"emailPlaceholder": "请输入邮箱",
"status": "状态",
"remark": "备注",
"remarkPlaceholder": "请输入备注信息",
"newPassword": "新密码",
"newPasswordPlaceholder": "请输入新密码",
"confirmNewPassword": "确认新密码",
"confirmNewPasswordPlaceholder": "请再次输入新密码",
"currentRoles": "当前角色",
"availableRoles": "可分配角色",
"basicInfo": "基本信息",
"roleInfo": "角色信息",
"statusInfo": "状态信息",
"remarkInfo": "备注信息",
"createTime": "创建时间",
"updateTime": "更新时间",
"lastLoginTime": "最后登录时间"
},
"validation": {
"accountNameRequired": "请输入账号名称",
"usernameRequired": "请输入用户名",
"usernameExists": "用户名已存在",
"passwordRequired": "请输入密码",
"passwordStrength": "密码强度不足至少8位且包含字母、数字",
"confirmPasswordRequired": "请再次输入密码",
"passwordNotMatch": "两次输入的密码不一致",
"phoneRequired": "请输入手机号",
"phonePattern": "请输入正确的手机号格式",
"emailPattern": "请输入正确的邮箱格式",
"newPasswordRequired": "请输入新密码",
"atLeastOneRole": "账号至少需要保留一个角色"
},
"messages": {
"createSuccess": "账号创建成功",
"updateSuccess": "账号更新成功",
"deleteSuccess": "账号删除成功",
"passwordChangeSuccess": "密码修改成功",
"roleAssignSuccess": "角色分配成功",
"statusChangeSuccess": "状态切换成功",
"deleteConfirm": "确定要删除账号 {name} 吗?此操作不可撤销",
"cannotDeleteSelf": "不能删除当前登录账号",
"cannotDisableSelf": "不能禁用当前登录账号",
"noData": "暂无账号数据,点击新增按钮创建账号",
"passwordHint": "如需修改密码请使用修改密码功能",
"disabledLoginHint": "账号已被禁用,请联系管理员"
}
},
"commission": {
"menu": {
"management": "佣金管理",
"withdrawal": "提现审批",
"withdrawalSettings": "提现配置",
"myCommission": "我的佣金",
"agentCommission": "代理商佣金"
},
"table": {
"withdrawalNo": "提现单号",
"applicant": "申请人",
"applicantAccount": "申请账号",
"withdrawalAmount": "提现金额",
"fee": "手续费",
"actualAmount": "实际到账",
"withdrawalMethod": "提现方式",
"accountInfo": "收款账户",
"accountName": "账户名称",
"accountNumber": "账号",
"bankName": "银行名称",
"status": "状态",
"rejectReason": "拒绝原因",
"applyTime": "申请时间",
"approveTime": "审批时间",
"transferTime": "到账时间",
"actions": "操作",
"commissionNo": "佣金单号",
"commissionType": "佣金类型",
"commissionAmount": "佣金金额",
"orderNo": "关联订单号",
"sourceUser": "来源用户",
"beneficiary": "受益人",
"settleTime": "结算时间",
"withdrawTime": "提现时间",
"remark": "备注"
},
"status": {
"pending": "待审核",
"approved": "已通过",
"rejected": "已拒绝",
"completed": "已到账",
"frozen": "已冻结",
"unfreezing": "解冻中",
"settled": "已发放",
"invalid": "已失效"
},
"type": {
"oneTime": "一次性佣金",
"longTerm": "长期佣金"
},
"method": {
"alipay": "支付宝",
"wechat": "微信",
"bank": "银行卡"
},
"form": {
"withdrawalAmount": "提现金额",
"withdrawalAmountPlaceholder": "请输入提现金额",
"withdrawalMethod": "提现方式",
"withdrawalMethodPlaceholder": "请选择提现方式",
"accountName": "账户名称",
"accountNamePlaceholder": "请输入账户名称",
"accountNumber": "账号",
"accountNumberPlaceholder": "请输入账号",
"bankName": "银行名称",
"bankNamePlaceholder": "请输入银行名称",
"alipayAccount": "支付宝账号",
"alipayAccountPlaceholder": "请输入支付宝账号",
"wechatAccount": "微信账号",
"wechatAccountPlaceholder": "请输入微信账号",
"rejectReason": "拒绝原因",
"rejectReasonPlaceholder": "请输入拒绝原因",
"remark": "备注",
"remarkPlaceholder": "请输入备注信息",
"minWithdrawal": "最低提现金额",
"minWithdrawalPlaceholder": "请输入最低提现金额",
"maxWithdrawal": "最高提现金额",
"maxWithdrawalPlaceholder": "请输入最高提现金额",
"feeRate": "手续费率",
"feeRatePlaceholder": "请输入手续费率(%",
"availableBalance": "可提现余额",
"totalCommission": "累计佣金",
"frozenAmount": "冻结金额",
"withdrawnAmount": "已提现金额"
},
"buttons": {
"approve": "审批通过",
"reject": "拒绝",
"applyWithdrawal": "提交提现申请",
"viewDetail": "查看详情",
"export": "导出",
"batchApprove": "批量通过",
"batchReject": "批量拒绝"
},
"messages": {
"approveSuccess": "审批通过成功",
"rejectSuccess": "拒绝成功",
"withdrawalSuccess": "提现申请提交成功",
"updateSuccess": "更新成功",
"deleteSuccess": "删除成功",
"approveConfirm": "确定要通过该提现申请吗?",
"rejectConfirm": "确定要拒绝该提现申请吗?",
"batchApproveConfirm": "确定要批量通过选中的 {count} 条提现申请吗?",
"batchRejectConfirm": "确定要批量拒绝选中的 {count} 条提现申请吗?",
"insufficientBalance": "可提现余额不足",
"belowMinAmount": "提现金额不能低于最低提现金额",
"exceedMaxAmount": "提现金额不能超过最高提现金额",
"noData": "暂无数据"
},
"validation": {
"withdrawalAmountRequired": "请输入提现金额",
"withdrawalAmountInvalid": "提现金额必须大于0",
"withdrawalMethodRequired": "请选择提现方式",
"accountNameRequired": "请输入账户名称",
"accountNumberRequired": "请输入账号",
"bankNameRequired": "请输入银行名称",
"rejectReasonRequired": "请输入拒绝原因",
"remarkRequired": "请输入备注",
"minWithdrawalRequired": "请输入最低提现金额",
"maxWithdrawalRequired": "请输入最高提现金额",
"feeRateRequired": "请输入手续费率",
"feeRateInvalid": "手续费率必须在0-100之间"
},
"searchForm": {
"withdrawalNo": "提现单号",
"withdrawalNoPlaceholder": "请输入提现单号",
"applicant": "申请人",
"applicantPlaceholder": "请输入申请人",
"shopName": "店铺名称",
"shopNamePlaceholder": "请输入店铺名称",
"status": "状态",
"statusPlaceholder": "请选择状态",
"withdrawalMethod": "提现方式",
"withdrawalMethodPlaceholder": "请选择提现方式",
"dateRange": "申请时间",
"dateRangePlaceholder": ["开始日期", "结束日期"],
"commissionType": "佣金类型",
"commissionTypePlaceholder": "请选择佣金类型"
},
"dialog": {
"approve": "审批提现申请",
"reject": "拒绝提现申请",
"detail": "提现详情",
"applyWithdrawal": "提交提现申请",
"withdrawalSettings": "提现配置"
},
"summary": {
"title": "佣金统计",
"totalCommission": "累计佣金",
"availableBalance": "可提现余额",
"frozenAmount": "冻结金额",
"withdrawnAmount": "已提现金额",
"pendingAmount": "待审核金额",
"todayCommission": "今日佣金"
}
},
"seriesBinding": {
"buttons": {
"batchSetSeries": "批量设置套餐系列",
"clearBinding": "清除绑定"
},
"dialog": {
"title": "批量设置套餐系列绑定",
"titleCard": "批量设置卡的套餐系列绑定",
"titleDevice": "批量设置设备的套餐系列绑定"
},
"form": {
"seriesAllocation": "套餐系列分配",
"seriesAllocationPlaceholder": "请选择套餐系列分配",
"clearBindingOption": "清除关联",
"selectedCount": "已选择 {count} 项"
},
"messages": {
"noSelection": "请先选择要设置的项",
"setSuccess": "套餐系列绑定设置成功",
"setFailed": "套餐系列绑定设置失败",
"partialSuccess": "部分设置成功:成功 {success} 项,失败 {fail} 项",
"confirmSet": "确定要为选中的 {count} 项设置套餐系列绑定吗?",
"confirmClear": "确定要清除选中的 {count} 项的套餐系列绑定吗?"
},
"result": {
"title": "设置结果",
"successCount": "成功数量",
"failCount": "失败数量",
"failedItems": "失败项详情",
"iccid": "ICCID",
"deviceId": "设备ID",
"deviceNo": "设备号",
"reason": "失败原因"
}
},
"orderManagement": {
"title": "订单管理",
"orderList": "订单列表",
"orderDetail": "订单详情",
"orderItems": "订单明细",
"createOrder": "创建订单",
"searchForm": {
"orderNo": "订单号",
"orderNoPlaceholder": "请输入订单号",
"paymentStatus": "支付状态",
"paymentStatusPlaceholder": "请选择支付状态",
"orderType": "订单类型",
"orderTypePlaceholder": "请选择订单类型",
"dateRange": "创建时间",
"startDate": "开始时间",
"endDate": "结束时间"
},
"table": {
"id": "订单ID",
"orderNo": "订单号",
"orderType": "订单类型",
"buyerId": "买家ID",
"buyerType": "买家类型",
"paymentStatus": "支付状态",
"paymentMethod": "支付方式",
"totalAmount": "订单金额",
"paidAt": "支付时间",
"commissionStatus": "佣金状态",
"createdAt": "创建时间",
"updatedAt": "更新时间",
"operation": "操作"
},
"orderType": {
"singleCard": "单卡购买",
"device": "设备购买"
},
"buyerType": {
"personal": "个人客户",
"agent": "代理商"
},
"paymentStatus": {
"pending": "待支付",
"paid": "已支付",
"cancelled": "已取消",
"refunded": "已退款"
},
"paymentMethod": {
"wallet": "钱包支付",
"wechat": "微信支付",
"alipay": "支付宝支付",
"offline": "线下支付"
},
"commissionStatus": {
"notApplicable": "不适用",
"pending": "待结算",
"settled": "已结算"
},
"actions": {
"viewDetail": "查看详情",
"cancel": "取消",
"confirm": "确定",
"submit": "提交",
"close": "关闭"
},
"createForm": {
"packageIds": "选择套餐",
"packageIdsPlaceholder": "请选择套餐",
"iotCardId": "ICCID",
"iotCardIdPlaceholder": "请选择ICCID卡",
"deviceId": "设备号",
"deviceIdPlaceholder": "请选择设备号"
},
"items": {
"packageName": "套餐名称",
"quantity": "数量",
"unitPrice": "单价",
"amount": "小计"
},
"messages": {
"createSuccess": "订单创建成功",
"createFailed": "订单创建失败",
"cancelSuccess": "订单取消成功",
"cancelFailed": "订单取消失败",
"cancelConfirm": "取消订单确认",
"cancelConfirmText": "确定要取消该订单吗?取消后不可恢复",
"cannotCancelPaid": "已支付的订单无法取消",
"cannotCancelCancelled": "订单已取消",
"cannotCancelRefunded": "已退款的订单无法取消",
"loadFailed": "加载订单数据失败",
"noData": "暂无订单数据"
},
"validation": {
"orderTypeRequired": "请选择订单类型",
"packageIdsRequired": "请至少选择一个套餐",
"iotCardRequired": "请选择IoT卡",
"deviceRequired": "请选择设备",
"packagesMaxLimit": "最多只能选择10个套餐"
}
},
"enterpriseDevices": {
"title": "企业设备列表",
"searchForm": {
"enterpriseId": "企业ID",
"enterpriseIdPlaceholder": "请输入企业ID",
"deviceNo": "设备号",
"deviceNoPlaceholder": "请输入设备号"
},
"table": {
"deviceId": "设备ID",
"deviceNo": "设备号",
"deviceName": "设备名称",
"deviceModel": "设备型号",
"cardCount": "绑定卡数量",
"authorizedAt": "授权时间",
"operation": "操作"
},
"buttons": {
"allocateDevices": "授权设备",
"recallDevices": "撤销授权",
"refresh": "刷新"
},
"dialog": {
"allocateTitle": "授权设备给企业",
"recallTitle": "撤销设备授权",
"resultTitle": "操作结果"
},
"form": {
"deviceNos": "设备号列表",
"deviceNosPlaceholder": "请输入设备号,支持换行或逗号分隔",
"deviceNosHint": "每行一个设备号或使用逗号分隔最多100个",
"remark": "备注",
"remarkPlaceholder": "请输入授权备注(可选)",
"selectedDevices": "已选择设备",
"selectedCount": "已选择 {count} 个设备"
},
"result": {
"successCount": "成功数量",
"failCount": "失败数量",
"authorizedDevices": "已授权设备",
"failedItems": "失败项",
"deviceNo": "设备号",
"deviceId": "设备ID",
"cardCount": "绑定卡数",
"reason": "失败原因"
},
"messages": {
"allocateSuccess": "设备授权成功",
"allocateFailed": "设备授权失败",
"allocatePartialSuccess": "部分设备授权成功:成功 {success} 个,失败 {fail} 个",
"recallSuccess": "撤销授权成功",
"recallFailed": "撤销授权失败",
"recallPartialSuccess": "部分设备撤销成功:成功 {success} 个,失败 {fail} 个",
"recallConfirm": "撤销授权确认",
"recallConfirmText": "确定要撤销选中的 {count} 个设备的授权吗?",
"noSelection": "请先选择要撤销的设备",
"deviceNosRequired": "请输入设备号",
"deviceNosEmpty": "设备号列表不能为空",
"deviceNosMaxLimit": "设备号数量不能超过100个",
"invalidDeviceNos": "存在无效的设备号格式",
"loadFailed": "加载设备列表失败",
"noData": "暂无设备数据"
},
"validation": {
"deviceNosRequired": "请输入设备号列表",
"deviceNosMaxLength": "设备号数量不能超过100个"
}
} }
} }

View File

@@ -312,7 +312,8 @@ export interface StandaloneCardQueryParams extends PaginationParams {
shop_id?: number // 分销商ID shop_id?: number // 分销商ID
iccid?: string // ICCID(模糊查询) iccid?: string // ICCID(模糊查询)
msisdn?: string // 卡接入号(模糊查询) msisdn?: string // 卡接入号(模糊查询)
virtual_no?: string // 虚拟号(模糊查询) virtual_no?: string // 虚拟号(模糊查询)
device_virtual_no?: string // 绑定设备虚拟号(模糊查询)
batch_no?: string // 批次号 batch_no?: string // 批次号
package_id?: number // 套餐ID package_id?: number // 套餐ID
is_distributed?: boolean // 是否已分销 is_distributed?: boolean // 是否已分销
@@ -328,7 +329,8 @@ export interface StandaloneIotCard {
iccid: string // ICCID iccid: string // ICCID
imsi?: string // IMSI (可选) imsi?: string // IMSI (可选)
msisdn?: string // 卡接入号 (可选) msisdn?: string // 卡接入号 (可选)
virtual_no?: string // 虚拟号(可空) virtual_no?: string // 虚拟号(可空)
device_virtual_no?: string // 绑定设备虚拟号(可空)
carrier_id: number // 运营商ID carrier_id: number // 运营商ID
carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电) carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)
carrier_name: string // 运营商名称 carrier_name: string // 运营商名称

View File

@@ -94,6 +94,7 @@ export interface CreateOrderRequest {
identifier?: string // 资产标识符ICCID 或 VirtualNo identifier?: string // 资产标识符ICCID 或 VirtualNo
package_ids: number[] // 套餐ID列表 package_ids: number[] // 套餐ID列表
payment_method: OrderPaymentMethod // 支付方式 payment_method: OrderPaymentMethod // 支付方式
payment_voucher_key?: string // 线下支付凭证(仅 offline 必填)
} }
// 创建订单响应 (返回订单详情) // 创建订单响应 (返回订单详情)

View File

@@ -205,6 +205,8 @@ export interface SeriesSelectOption {
id: number id: number
series_name: string series_name: string
series_code: string series_code: string
enable_one_time_commission?: boolean // 是否启用一次性佣金
one_time_commission_config?: SeriesOneTimeCommissionConfig // 一次性佣金配置
} }
// ==================== 代理系列授权 (新接口) ==================== // ==================== 代理系列授权 (新接口) ====================

View File

@@ -13,6 +13,7 @@ export interface ShopResponse {
shop_code: string // 店铺编号 shop_code: string // 店铺编号
level: number // 店铺层级 (1-7级) level: number // 店铺层级 (1-7级)
parent_id: number | null // 上级店铺ID parent_id: number | null // 上级店铺ID
parent_shop_name?: string // 上级店铺名称
province: string // 省份 province: string // 省份
city: string // 城市 city: string // 城市
district: string // 区县 district: string // 区县
@@ -26,6 +27,7 @@ export interface ShopResponse {
export interface ShopQueryParams extends PaginationParams { export interface ShopQueryParams extends PaginationParams {
shop_name?: string // 店铺名称模糊查询 shop_name?: string // 店铺名称模糊查询
shop_code?: string // 店铺编号模糊查询 shop_code?: string // 店铺编号模糊查询
parent_shop_name?: string // 上级店铺名称模糊查询
parent_id?: number | null // 上级店铺ID parent_id?: number | null // 上级店铺ID
level?: number | null // 店铺层级 (1-7级) level?: number | null // 店铺层级 (1-7级)
status?: number | null // 状态 (0:禁用, 1:启用) status?: number | null // 状态 (0:禁用, 1:启用)

View File

@@ -87,6 +87,7 @@ declare module 'vue' {
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar'] ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
@@ -104,6 +105,7 @@ declare module 'vue' {
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']

View File

@@ -14,7 +14,7 @@ export const usernameRules = (t: (key: string) => string): FormItemRule[] => [
trigger: 'blur' trigger: 'blur'
}, },
{ {
min: 3, min: 2,
max: 20, max: 20,
message: t('login.validation.usernameLength'), message: t('login.validation.usernameLength'),
trigger: 'blur' trigger: 'blur'

View File

@@ -104,20 +104,15 @@
<!-- 只有非代理账号才显示归属店铺选择 --> <!-- 只有非代理账号才显示归属店铺选择 -->
<ElCol :span="12" v-if="!isAgentAccount"> <ElCol :span="12" v-if="!isAgentAccount">
<ElFormItem label="归属店铺" prop="owner_shop_id"> <ElFormItem label="归属店铺" prop="owner_shop_id">
<ElTreeSelect <ElCascader
v-model="form.owner_shop_id" v-model="form.owner_shop_id"
:data="shopTreeData" :options="shopCascadeOptions"
:props="shopCascadeProps"
placeholder="请选择归属店铺" placeholder="请选择归属店铺"
filterable filterable
clearable clearable
check-strictly
:render-after-expand="false"
:props="{
label: 'shop_name',
value: 'id',
children: 'children'
}"
style="width: 100%" style="width: 100%"
@change="handleShopChange"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
@@ -240,6 +235,37 @@
const currentEnterpriseId = ref<number>(0) const currentEnterpriseId = ref<number>(0)
const shopTreeData = ref<ShopResponse[]>([]) const shopTreeData = ref<ShopResponse[]>([])
// 店铺级联选择相关
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
lazy: true,
checkStrictly: true,
lazyLoad: async (node: any, resolve: any) => {
const { level, value } = node
const parentId = level === 0 ? undefined : value
try {
const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else {
resolve([])
}
} catch (error) {
console.error('加载店铺级联数据失败:', error)
resolve([])
}
},
value: 'value',
label: 'label',
children: 'children'
}
// 搜索表单初始值 // 搜索表单初始值
const initialSearchState = { const initialSearchState = {
enterprise_name: '', enterprise_name: '',
@@ -510,19 +536,17 @@
} }
}) })
// 加载店铺列表(获取所有店铺构建树形结构) // 加载店铺列表 - 改用级联查询
const loadShopList = async () => { const loadShopList = async () => {
shopLoading.value = true shopLoading.value = true
try { try {
const params: any = { const res = await ShopService.getShopsCascade({ parent_id: undefined })
page: 1,
page_size: 9999 // 获取所有数据用于构建树形结构
}
const res = await ShopService.getShops(params)
if (res.code === 0) { if (res.code === 0) {
const items = res.data.items || [] shopCascadeOptions.value = (res.data || []).map((item: any) => ({
// 构建树形数据 value: item.id,
shopTreeData.value = buildTreeData(items) label: item.shop_name,
leaf: !item.has_children
}))
} }
} catch (error) { } catch (error) {
console.error('获取店铺列表失败:', error) console.error('获取店铺列表失败:', error)
@@ -531,6 +555,15 @@
} }
} }
// 处理店铺选择变化
const handleShopChange = (value: any) => {
if (Array.isArray(value) && value.length > 0) {
form.owner_shop_id = value[value.length - 1]
} else {
form.owner_shop_id = null
}
}
// 构建树形数据 // 构建树形数据
const buildTreeData = (items: ShopResponse[]) => { const buildTreeData = (items: ShopResponse[]) => {
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>() const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()

File diff suppressed because it is too large Load Diff

View File

@@ -76,7 +76,7 @@
<ElFormItem label="已选设备数"> <ElFormItem label="已选设备数">
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> <span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span>
</ElFormItem> </ElFormItem>
<ElFormItem label="目标店铺" prop="target_shop_id"> <ElFormItem label="店铺" prop="target_shop_id">
<ElTreeSelect <ElTreeSelect
v-model="allocateForm.target_shop_id" v-model="allocateForm.target_shop_id"
:data="shopTreeData" :data="shopTreeData"

View File

@@ -5,7 +5,7 @@
<ElCard shadow="never" style="margin-bottom: 16px"> <ElCard shadow="never" style="margin-bottom: 16px">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<span>{{ $t('enterpriseDevices.title') }}</span> <span>企业设备列表</span>
<ElButton @click="goBack">{{ $t('common.cancel') }}</ElButton> <ElButton @click="goBack">{{ $t('common.cancel') }}</ElButton>
</div> </div>
</template> </template>
@@ -42,7 +42,7 @@
@click="showAllocateDialog" @click="showAllocateDialog"
v-permission="'enterprise_device:allocate'" v-permission="'enterprise_device:allocate'"
> >
{{ $t('enterpriseDevices.buttons.allocateDevices') }} 授权设备
</ElButton> </ElButton>
<ElButton <ElButton
type="warning" type="warning"
@@ -50,7 +50,7 @@
@click="showRecallDialog" @click="showRecallDialog"
v-permission="'enterprise_device:recall'" v-permission="'enterprise_device:recall'"
> >
{{ $t('enterpriseDevices.buttons.recallDevices') }} 撤销授权
</ElButton> </ElButton>
</template> </template>
</ArtTableHeader> </ArtTableHeader>
@@ -78,7 +78,7 @@
<!-- 授权设备对话框 --> <!-- 授权设备对话框 -->
<ElDialog <ElDialog
v-model="allocateDialogVisible" v-model="allocateDialogVisible"
:title="$t('enterpriseDevices.dialog.allocateTitle')" title="授权设备给企业"
width="75%" width="75%"
@close="handleAllocateDialogClose" @close="handleAllocateDialogClose"
> >
@@ -137,19 +137,13 @@
<!-- 撤销授权对话框 --> <!-- 撤销授权对话框 -->
<ElDialog <ElDialog
v-model="recallDialogVisible" v-model="recallDialogVisible"
:title="$t('enterpriseDevices.dialog.recallTitle')" title="撤销设备授权"
width="600px" width="600px"
@close="handleRecallDialogClose" @close="handleRecallDialogClose"
> >
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules"> <ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules">
<ElFormItem :label="$t('enterpriseDevices.form.selectedDevices')"> <ElFormItem label="已选择设备">
<div> <div>已选择 {{ selectedDevices.length }} 个设备</div>
{{
$t('enterpriseDevices.form.selectedCount', {
count: selectedDevices.length
})
}}
</div>
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
<template #footer> <template #footer>
@@ -165,14 +159,14 @@
<!-- 结果对话框 --> <!-- 结果对话框 -->
<ElDialog <ElDialog
v-model="resultDialogVisible" v-model="resultDialogVisible"
:title="$t('enterpriseDevices.dialog.resultTitle')" title="操作结果"
width="700px" width="700px"
> >
<ElDescriptions :column="2" border> <ElDescriptions :column="2" border>
<ElDescriptionsItem :label="$t('enterpriseDevices.result.successCount')"> <ElDescriptionsItem label="成功数量">
<ElTag type="success">{{ operationResult.success_count }}</ElTag> <ElTag type="success">{{ operationResult.success_count }}</ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem :label="$t('enterpriseDevices.result.failCount')"> <ElDescriptionsItem label="失败数量">
<ElTag type="danger">{{ operationResult.fail_count }}</ElTag> <ElTag type="danger">{{ operationResult.fail_count }}</ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
</ElDescriptions> </ElDescriptions>
@@ -182,16 +176,14 @@
v-if="operationResult.failed_items && operationResult.failed_items.length > 0" v-if="operationResult.failed_items && operationResult.failed_items.length > 0"
style="margin-top: 20px" style="margin-top: 20px"
> >
<ElDivider content-position="left">{{ <ElDivider content-position="left">失败项</ElDivider>
$t('enterpriseDevices.result.failedItems')
}}</ElDivider>
<ElTable :data="operationResult.failed_items" border max-height="300"> <ElTable :data="operationResult.failed_items" border max-height="300">
<ElTableColumn <ElTableColumn
prop="virtual_no" prop="virtual_no"
:label="$t('enterpriseDevices.result.deviceNo')" label="设备号"
width="180" width="180"
/> />
<ElTableColumn prop="reason" :label="$t('enterpriseDevices.result.reason')" /> <ElTableColumn prop="reason" label="失败原因" />
</ElTable> </ElTable>
</div> </div>
@@ -202,21 +194,19 @@
" "
style="margin-top: 20px" style="margin-top: 20px"
> >
<ElDivider content-position="left">{{ <ElDivider content-position="left">已授权设备</ElDivider>
$t('enterpriseDevices.result.authorizedDevices')
}}</ElDivider>
<ElTable :data="operationResult.authorized_devices" border max-height="200"> <ElTable :data="operationResult.authorized_devices" border max-height="200">
<ElTableColumn <ElTableColumn
prop="virtual_no" prop="virtual_no"
:label="$t('enterpriseDevices.result.deviceNo')" label="设备号"
width="180" width="180"
/> />
<ElTableColumn <ElTableColumn
prop="device_id" prop="device_id"
:label="$t('enterpriseDevices.result.deviceId')" label="设备ID"
width="100" width="100"
/> />
<ElTableColumn :label="$t('enterpriseDevices.result.cardCount')"> <ElTableColumn label="绑定卡数">
<template #default="{ row }"> <template #default="{ row }">
{{ row.card_count || 0 }} {{ row.card_count || 0 }}
</template> </template>
@@ -259,7 +249,7 @@
defineOptions({ name: 'EnterpriseDevices' }) defineOptions({ name: 'EnterpriseDevices' })
const { t } = useI18n() const { t } = useI18n() // 仅保留用于 common.confirm, common.cancel
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const loading = ref(false) const loading = ref(false)
@@ -326,12 +316,12 @@
// 搜索表单配置 // 搜索表单配置
const searchFormItems: SearchFormItem[] = [ const searchFormItems: SearchFormItem[] = [
{ {
label: t('enterpriseDevices.searchForm.deviceNo'), label: '设备号',
prop: 'virtual_no', prop: 'virtual_no',
type: 'input', type: 'input',
config: { config: {
clearable: true, clearable: true,
placeholder: t('enterpriseDevices.searchForm.deviceNoPlaceholder') placeholder: '请输入设备号'
} }
} }
] ]
@@ -375,12 +365,12 @@
// 列配置 // 列配置
const columnOptions = [ const columnOptions = [
{ label: t('enterpriseDevices.table.deviceId'), prop: 'device_id' }, { label: '设备ID', prop: 'device_id' },
{ label: t('enterpriseDevices.table.deviceNo'), prop: 'virtual_no' }, { label: '设备号', prop: 'virtual_no' },
{ label: t('enterpriseDevices.table.deviceName'), prop: 'device_name' }, { label: '设备名称', prop: 'device_name' },
{ label: t('enterpriseDevices.table.deviceModel'), prop: 'device_model' }, { label: '设备型号', prop: 'device_model' },
{ label: t('enterpriseDevices.table.cardCount'), prop: 'card_count' }, { label: '绑定卡数量', prop: 'card_count' },
{ label: t('enterpriseDevices.table.authorizedAt'), prop: 'authorized_at' } { label: '授权时间', prop: 'authorized_at' }
] ]
const deviceList = ref<EnterpriseDeviceItem[]>([]) const deviceList = ref<EnterpriseDeviceItem[]>([])
@@ -421,32 +411,32 @@
const { columnChecks, columns } = useCheckedColumns(() => [ const { columnChecks, columns } = useCheckedColumns(() => [
{ {
prop: 'device_id', prop: 'device_id',
label: t('enterpriseDevices.table.deviceId'), label: '设备ID',
width: 100 width: 100
}, },
{ {
prop: 'virtual_no', prop: 'virtual_no',
label: t('enterpriseDevices.table.deviceNo'), label: '设备号',
minWidth: 150 minWidth: 150
}, },
{ {
prop: 'device_name', prop: 'device_name',
label: t('enterpriseDevices.table.deviceName'), label: '设备名称',
minWidth: 150 minWidth: 150
}, },
{ {
prop: 'device_model', prop: 'device_model',
label: t('enterpriseDevices.table.deviceModel'), label: '设备型号',
width: 120 width: 120
}, },
{ {
prop: 'card_count', prop: 'card_count',
label: t('enterpriseDevices.table.cardCount'), label: '绑定卡数量',
width: 100 width: 100
}, },
{ {
prop: 'authorized_at', prop: 'authorized_at',
label: t('enterpriseDevices.table.authorizedAt'), label: '授权时间',
width: 180, width: 180,
formatter: (row: EnterpriseDeviceItem) => formatter: (row: EnterpriseDeviceItem) =>
row.authorized_at ? formatDateTime(row.authorized_at) : '-' row.authorized_at ? formatDateTime(row.authorized_at) : '-'
@@ -499,7 +489,7 @@
getEnterpriseInfo() getEnterpriseInfo()
getTableData() getTableData()
} else { } else {
ElMessage.error(t('enterpriseDevices.messages.loadFailed')) ElMessage.error('加载设备列表失败')
goBack() goBack()
} }
}) })
@@ -686,16 +676,13 @@
// 显示成功消息 // 显示成功消息
if (res.data.success_count > 0 && res.data.fail_count === 0) { if (res.data.success_count > 0 && res.data.fail_count === 0) {
ElMessage.success(t('enterpriseDevices.messages.allocateSuccess')) ElMessage.success('设备授权成功')
} else if (res.data.success_count > 0 && res.data.fail_count > 0) { } else if (res.data.success_count > 0 && res.data.fail_count > 0) {
ElMessage.warning( ElMessage.warning(
t('enterpriseDevices.messages.allocatePartialSuccess', { `部分设备授权成功:成功 ${res.data.success_count} 个,失败 ${res.data.fail_count} 个`
success: res.data.success_count,
fail: res.data.fail_count
})
) )
} else { } else {
ElMessage.error(t('enterpriseDevices.messages.allocateFailed')) ElMessage.error('设备授权失败')
} }
getTableData() getTableData()
@@ -715,7 +702,7 @@
// 显示撤销授权对话框 // 显示撤销授权对话框
const showRecallDialog = () => { const showRecallDialog = () => {
if (selectedDevices.value.length === 0) { if (selectedDevices.value.length === 0) {
ElMessage.warning(t('enterpriseDevices.messages.noSelection')) ElMessage.warning('请先选择要撤销的设备')
return return
} }
recallDialogVisible.value = true recallDialogVisible.value = true
@@ -729,10 +716,8 @@
if (!recallFormRef.value) return if (!recallFormRef.value) return
ElMessageBox.confirm( ElMessageBox.confirm(
t('enterpriseDevices.messages.recallConfirmText', { `确定要撤销选中的 ${selectedDevices.value.length} 个设备的授权吗?`,
count: selectedDevices.value.length '撤销授权确认',
}),
t('enterpriseDevices.messages.recallConfirm'),
{ {
confirmButtonText: t('common.confirm'), confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),
@@ -753,16 +738,13 @@
// 显示成功消息 // 显示成功消息
if (res.data.success_count > 0 && res.data.fail_count === 0) { if (res.data.success_count > 0 && res.data.fail_count === 0) {
ElMessage.success(t('enterpriseDevices.messages.recallSuccess')) ElMessage.success('撤销授权成功')
} else if (res.data.success_count > 0 && res.data.fail_count > 0) { } else if (res.data.success_count > 0 && res.data.fail_count > 0) {
ElMessage.warning( ElMessage.warning(
t('enterpriseDevices.messages.recallPartialSuccess', { `部分设备撤销成功:成功 ${res.data.success_count} 个,失败 ${res.data.fail_count} 个`
success: res.data.success_count,
fail: res.data.fail_count
})
) )
} else { } else {
ElMessage.error(t('enterpriseDevices.messages.recallFailed')) ElMessage.error('撤销授权失败')
} }
// 清空选择 // 清空选择
@@ -774,7 +756,7 @@
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
ElMessage.error(t('enterpriseDevices.messages.recallFailed')) ElMessage.error('撤销授权失败')
} finally { } finally {
recallLoading.value = false recallLoading.value = false
} }

View File

@@ -93,7 +93,7 @@
:rules="allocateRules" :rules="allocateRules"
label-width="120px" label-width="120px"
> >
<ElFormItem label="目标店铺" prop="to_shop_id"> <ElFormItem label="店铺" prop="to_shop_id">
<ElSelect <ElSelect
v-model="allocateForm.to_shop_id" v-model="allocateForm.to_shop_id"
placeholder="请选择或搜索目标店铺" placeholder="请选择或搜索目标店铺"
@@ -669,6 +669,7 @@
iccid: string iccid: string
msisdn: string msisdn: string
virtual_no: string virtual_no: string
device_virtual_no: string
is_distributed: undefined | number is_distributed: undefined | number
[key: string]: any [key: string]: any
} = { } = {
@@ -677,6 +678,7 @@
iccid: '', iccid: '',
msisdn: '', msisdn: '',
virtual_no: '', virtual_no: '',
device_virtual_no: '',
is_distributed: undefined is_distributed: undefined
} }
@@ -827,12 +829,21 @@
} }
}, },
{ {
label: '虚拟号', label: '虚拟号',
prop: 'virtual_no', prop: 'virtual_no',
type: 'input', type: 'input',
config: { config: {
clearable: true, clearable: true,
placeholder: '请输入虚拟号' placeholder: '请输入虚拟号'
}
},
{
label: '绑定设备虚拟号',
prop: 'device_virtual_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入绑定设备虚拟号'
} }
}, },
{ {
@@ -854,7 +865,8 @@
const columnOptions = [ const columnOptions = [
{ label: 'ICCID', prop: 'iccid' }, { label: 'ICCID', prop: 'iccid' },
{ label: '卡接入号', prop: 'msisdn' }, { label: '卡接入号', prop: 'msisdn' },
{ label: '虚拟号', prop: 'virtual_no' }, { label: '虚拟号', prop: 'virtual_no' },
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
{ label: '卡业务类型', prop: 'card_category' }, { label: '卡业务类型', prop: 'card_category' },
{ label: '运营商', prop: 'carrier_name' }, { label: '运营商', prop: 'carrier_name' },
{ label: '店铺名称', prop: 'shop_name' }, { label: '店铺名称', prop: 'shop_name' },
@@ -987,9 +999,15 @@
}, },
{ {
prop: 'virtual_no', prop: 'virtual_no',
label: '虚拟号', label: '虚拟号',
width: 160, width: 160,
formatter: (row: StandaloneIotCard) => row.virtual_no formatter: (row: StandaloneIotCard) => row.virtual_no || '-'
},
{
prop: 'device_virtual_no',
label: '绑定设备虚拟号',
width: 160,
formatter: (row: StandaloneIotCard) => row.device_virtual_no || '-'
}, },
{ {
prop: 'card_category', prop: 'card_category',

View File

@@ -52,7 +52,7 @@
</div> </div>
<!-- 导入对话框 --> <!-- 导入对话框 -->
<ElDialog v-model="importDialogVisible" title="批量导入设备" width="700px" align-center> <ElDialog v-model="importDialogVisible" title="批量导入设备" width="40%" align-center>
<ElAlert type="info" :closable="false" style="margin-bottom: 20px"> <ElAlert type="info" :closable="false" style="margin-bottom: 20px">
<template #title> <template #title>
<div style="line-height: 1.8"> <div style="line-height: 1.8">
@@ -62,7 +62,7 @@
<p>3. 列格式请设置为文本格式避免长数字被转为科学计数法</p> <p>3. 列格式请设置为文本格式避免长数字被转为科学计数法</p>
<p <p
>4. >4.
必填列virtual_no设备device_name设备名称device_model设备型号device_type设备类型imei设备IMEI号</p 必填列virtual_no虚拟device_name设备名称device_model设备型号device_type设备类型imei设备IMEI号</p
> >
<p <p
>5. 可选列manufacturer制造商max_sim_slots最大插槽数默认4iccid_1 ~ >5. 可选列manufacturer制造商max_sim_slots最大插槽数默认4iccid_1 ~

View File

@@ -10,22 +10,22 @@
</i> </i>
</div> </div>
<ElDropdown @command="changeLanguage" popper-class="langDropDownStyle"> <ElDropdown @command="changeLanguage" popper-class="langDropDownStyle">
<div class="btn language-btn"> <!--<div class="btn language-btn">-->
<i class="iconfont-sys icon-language">&#xe611;</i> <!-- <i class="iconfont-sys icon-language">&#xe611;</i>-->
</div> <!--</div>-->
<template #dropdown> <!--<template #dropdown>-->
<ElDropdownMenu> <!-- <ElDropdownMenu>-->
<div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item"> <!-- <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">-->
<ElDropdownItem <!-- <ElDropdownItem-->
:command="lang.value" <!-- :command="lang.value"-->
:class="{ 'is-selected': locale === lang.value }" <!-- :class="{ 'is-selected': locale === lang.value }"-->
> <!-- >-->
<span class="menu-txt">{{ lang.label }}</span> <!-- <span class="menu-txt">{{ lang.label }}</span>-->
<i v-if="locale === lang.value" class="iconfont-sys icon-check">&#xe621;</i> <!-- <i v-if="locale === lang.value" class="iconfont-sys icon-check">&#xe621;</i>-->
</ElDropdownItem> <!-- </ElDropdownItem>-->
</div> <!-- </div>-->
</ElDropdownMenu> <!-- </ElDropdownMenu>-->
</template> <!--</template>-->
</ElDropdown> </ElDropdown>
</div> </div>
<div class="header"> <div class="header">
@@ -97,9 +97,6 @@
<script setup lang="ts"> <script setup lang="ts">
import AppConfig from '@/config' import AppConfig from '@/config'
import { RoutesAlias } from '@/router/routesAlias'
import { getCssVar } from '@/utils/ui'
import { languageOptions } from '@/locales'
import { LanguageEnum, SystemThemeEnum } from '@/enums/appEnum' import { LanguageEnum, SystemThemeEnum } from '@/enums/appEnum'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useSettingStore } from '@/store/modules/setting' import { useSettingStore } from '@/store/modules/setting'

View File

@@ -40,24 +40,24 @@
<!-- 拒绝提现对话框 --> <!-- 拒绝提现对话框 -->
<ElDialog <ElDialog
v-model="rejectDialogVisible" v-model="rejectDialogVisible"
:title="$t('commission.dialog.reject')" title="拒绝提现申请"
width="500px" width="500px"
> >
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px"> <ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
<ElFormItem :label="$t('commission.form.rejectReason')" prop="reject_reason"> <ElFormItem label="拒绝原因" prop="reject_reason">
<ElInput <ElInput
v-model="rejectForm.reject_reason" v-model="rejectForm.reject_reason"
type="textarea" type="textarea"
:rows="4" :rows="4"
:placeholder="$t('commission.form.rejectReasonPlaceholder')" placeholder="请输入拒绝原因"
/> />
</ElFormItem> </ElFormItem>
<ElFormItem :label="$t('commission.form.remark')" prop="remark"> <ElFormItem label="备注" prop="remark">
<ElInput <ElInput
v-model="rejectForm.remark" v-model="rejectForm.remark"
type="textarea" type="textarea"
:rows="3" :rows="3"
:placeholder="$t('commission.form.remarkPlaceholder')" placeholder="请输入备注信息"
/> />
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
@@ -94,7 +94,7 @@
defineOptions({ name: 'WithdrawalApproval' }) defineOptions({ name: 'WithdrawalApproval' })
const { t } = useI18n() const { t } = useI18n() // 仅保留用于 common.tips, common.confirm, common.cancel
const rejectDialogVisible = ref(false) const rejectDialogVisible = ref(false)
const loading = ref(false) const loading = ref(false)
@@ -116,50 +116,50 @@
// 提现状态选项 // 提现状态选项
const withdrawalStatusOptions = [ const withdrawalStatusOptions = [
{ label: t('commission.status.pending'), value: 1 }, { label: '待审核', value: 1 },
{ label: t('commission.status.approved'), value: 2 }, { label: '已通过', value: 2 },
{ label: t('commission.status.rejected'), value: 3 }, { label: '已拒绝', value: 3 },
{ label: t('commission.status.completed'), value: 4 } { label: '已到账', value: 4 }
] ]
// 搜索表单配置 // 搜索表单配置
const searchFormItems: SearchFormItem[] = [ const searchFormItems: SearchFormItem[] = [
{ {
label: t('commission.searchForm.status'), label: '状态',
prop: 'status', prop: 'status',
type: 'select', type: 'select',
options: withdrawalStatusOptions, options: withdrawalStatusOptions,
config: { config: {
clearable: true, clearable: true,
placeholder: t('commission.searchForm.statusPlaceholder') placeholder: '请选择状态'
} }
}, },
{ {
label: t('commission.searchForm.withdrawalNo'), label: '提现单号',
prop: 'withdrawal_no', prop: 'withdrawal_no',
type: 'input', type: 'input',
config: { config: {
clearable: true, clearable: true,
placeholder: t('commission.searchForm.withdrawalNoPlaceholder') placeholder: '请输入提现单号'
} }
}, },
{ {
label: t('commission.searchForm.shopName'), label: '店铺名称',
prop: 'shop_name', prop: 'shop_name',
type: 'input', type: 'input',
config: { config: {
clearable: true, clearable: true,
placeholder: t('commission.searchForm.shopNamePlaceholder') placeholder: '请输入店铺名称'
} }
}, },
{ {
label: t('commission.searchForm.dateRange'), label: '申请时间',
prop: 'dateRange', prop: 'dateRange',
type: 'daterange', type: 'daterange',
config: { config: {
clearable: true, clearable: true,
startPlaceholder: t('commission.searchForm.dateRangePlaceholder.0'), startPlaceholder: '开始日期',
endPlaceholder: t('commission.searchForm.dateRangePlaceholder.1'), endPlaceholder: '结束日期',
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
onChange: (val: [string, string] | null) => { onChange: (val: [string, string] | null) => {
if (val && val.length === 2) { if (val && val.length === 2) {
@@ -183,27 +183,27 @@
// 列配置 // 列配置
const columnOptions = [ const columnOptions = [
{ label: t('commission.table.withdrawalNo'), prop: 'withdrawal_no' }, { label: '提现单号', prop: 'withdrawal_no' },
{ label: t('commission.table.shopName'), prop: 'shop_name' }, { label: '店铺名称', prop: 'shop_name' },
{ label: t('commission.table.applicant'), prop: 'applicant_name' }, { label: '申请人', prop: 'applicant_name' },
{ label: t('commission.table.withdrawalAmount'), prop: 'amount' }, { label: '提现金额', prop: 'amount' },
{ label: t('commission.table.fee'), prop: 'fee' }, { label: '手续费', prop: 'fee' },
{ label: t('commission.table.actualAmount'), prop: 'actual_amount' }, { label: '实际到账', prop: 'actual_amount' },
{ label: t('commission.table.withdrawalMethod'), prop: 'withdrawal_method' }, { label: '提现方式', prop: 'withdrawal_method' },
{ label: t('commission.table.status'), prop: 'status' }, { label: '状态', prop: 'status' },
{ label: t('commission.table.applyTime'), prop: 'created_at' }, { label: '申请时间', prop: 'created_at' },
{ label: t('commission.table.approveTime'), prop: 'processed_at' }, { label: '审批时间', prop: 'processed_at' },
{ label: t('commission.table.actions'), prop: 'operation' } { label: '操作', prop: 'operation' }
] ]
const rejectFormRef = ref<FormInstance>() const rejectFormRef = ref<FormInstance>()
const rejectRules = reactive<FormRules>({ const rejectRules = reactive<FormRules>({
reject_reason: [ reject_reason: [
{ required: true, message: t('commission.validation.rejectReasonRequired'), trigger: 'blur' } { required: true, message: '请输入拒绝原因', trigger: 'blur' }
], ],
remark: [ remark: [
{ required: true, message: t('commission.validation.remarkRequired'), trigger: 'blur' } { required: true, message: '请输入备注', trigger: 'blur' }
] ]
}) })
@@ -218,43 +218,43 @@
const { columnChecks, columns } = useCheckedColumns(() => [ const { columnChecks, columns } = useCheckedColumns(() => [
{ {
prop: 'withdrawal_no', prop: 'withdrawal_no',
label: t('commission.table.withdrawalNo'), label: '提现单号',
minWidth: 180 minWidth: 180
}, },
{ {
prop: 'shop_name', prop: 'shop_name',
label: t('commission.table.shopName'), label: '店铺名称',
minWidth: 150 minWidth: 150
}, },
{ {
prop: 'applicant_name', prop: 'applicant_name',
label: t('commission.table.applicant'), label: '申请人',
width: 120 width: 120
}, },
{ {
prop: 'amount', prop: 'amount',
label: t('commission.table.withdrawalAmount'), label: '提现金额',
width: 120, width: 120,
align: 'right', align: 'right',
formatter: (row: WithdrawalRequestItem) => formatMoney(row.amount) formatter: (row: WithdrawalRequestItem) => formatMoney(row.amount)
}, },
{ {
prop: 'fee', prop: 'fee',
label: t('commission.table.fee'), label: '手续费',
width: 100, width: 100,
align: 'right', align: 'right',
formatter: (row: WithdrawalRequestItem) => formatMoney(row.fee) formatter: (row: WithdrawalRequestItem) => formatMoney(row.fee)
}, },
{ {
prop: 'actual_amount', prop: 'actual_amount',
label: t('commission.table.actualAmount'), label: '实际到账',
width: 120, width: 120,
align: 'right', align: 'right',
formatter: (row: WithdrawalRequestItem) => formatMoney(row.actual_amount) formatter: (row: WithdrawalRequestItem) => formatMoney(row.actual_amount)
}, },
{ {
prop: 'withdrawal_method', prop: 'withdrawal_method',
label: t('commission.table.withdrawalMethod'), label: '提现方式',
width: 120, width: 120,
formatter: (row: WithdrawalRequestItem) => { formatter: (row: WithdrawalRequestItem) => {
const method = WithdrawalMethodMap[row.withdrawal_method as WithdrawalMethod] const method = WithdrawalMethodMap[row.withdrawal_method as WithdrawalMethod]
@@ -263,7 +263,7 @@
}, },
{ {
prop: 'status', prop: 'status',
label: t('commission.table.status'), label: '状态',
width: 100, width: 100,
formatter: (row: WithdrawalRequestItem) => { formatter: (row: WithdrawalRequestItem) => {
const statusInfo = WithdrawalStatusMap[row.status as keyof typeof WithdrawalStatusMap] const statusInfo = WithdrawalStatusMap[row.status as keyof typeof WithdrawalStatusMap]
@@ -272,19 +272,19 @@
}, },
{ {
prop: 'created_at', prop: 'created_at',
label: t('commission.table.applyTime'), label: '申请时间',
width: 180, width: 180,
formatter: (row: WithdrawalRequestItem) => formatDateTime(row.created_at) formatter: (row: WithdrawalRequestItem) => formatDateTime(row.created_at)
}, },
{ {
prop: 'processed_at', prop: 'processed_at',
label: t('commission.table.approveTime'), label: '审批时间',
width: 180, width: 180,
formatter: (row: WithdrawalRequestItem) => formatDateTime(row.processed_at) formatter: (row: WithdrawalRequestItem) => formatDateTime(row.processed_at)
}, },
{ {
prop: 'operation', prop: 'operation',
label: t('commission.table.actions'), label: '操作',
width: 150, width: 150,
fixed: 'right', fixed: 'right',
formatter: (row: WithdrawalRequestItem) => { formatter: (row: WithdrawalRequestItem) => {
@@ -292,12 +292,12 @@
if (row.status === 1) { if (row.status === 1) {
return h('div', { style: 'display: flex; gap: 8px;' }, [ return h('div', { style: 'display: flex; gap: 8px;' }, [
h(ArtButtonTable, { h(ArtButtonTable, {
text: t('commission.buttons.approve'), text: '审批通过',
iconColor: '#67C23A', iconColor: '#67C23A',
onClick: () => handleApprove(row) onClick: () => handleApprove(row)
}), }),
h(ArtButtonTable, { h(ArtButtonTable, {
text: t('commission.buttons.reject'), text: '拒绝',
iconColor: '#F56C6C', iconColor: '#F56C6C',
onClick: () => showRejectDialog(row) onClick: () => showRejectDialog(row)
}) })
@@ -368,7 +368,7 @@
// 审批通过 // 审批通过
const handleApprove = (row: WithdrawalRequestItem) => { const handleApprove = (row: WithdrawalRequestItem) => {
ElMessageBox.confirm(t('commission.messages.approveConfirm'), t('common.tips'), { ElMessageBox.confirm('确定要通过该提现申请吗?', t('common.tips'), {
confirmButtonText: t('common.confirm'), confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),
type: 'warning' type: 'warning'
@@ -376,7 +376,7 @@
.then(async () => { .then(async () => {
try { try {
await CommissionService.approveWithdrawal(row.id) await CommissionService.approveWithdrawal(row.id)
ElMessage.success(t('commission.messages.approveSuccess')) ElMessage.success('审批通过成功')
getTableData() getTableData()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -407,7 +407,7 @@
reject_reason: rejectForm.reject_reason, reject_reason: rejectForm.reject_reason,
remark: rejectForm.remark remark: rejectForm.remark
}) })
ElMessage.success(t('commission.messages.rejectSuccess')) ElMessage.success('拒绝成功')
rejectDialogVisible.value = false rejectDialogVisible.value = false
rejectFormRef.value?.resetFields() rejectFormRef.value?.resetFields()
getTableData() getTableData()

View File

@@ -18,7 +18,12 @@
@refresh="handleRefresh" @refresh="handleRefresh"
> >
<template #left> <template #left>
<ElButton type="primary" @click="showCreateDialog" v-if="hasAuth('agent_recharge:create')">创建充值订单</ElButton> <ElButton
type="primary"
@click="showCreateDialog"
v-if="hasAuth('agent_recharge:create')"
>创建充值订单</ElButton
>
</template> </template>
</ArtTableHeader> </ArtTableHeader>
@@ -80,21 +85,16 @@
/> />
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem label="目标店铺" prop="shop_id"> <ElFormItem label="店铺" prop="shop_id">
<ElTreeSelect <ElCascader
v-model="createForm.shop_id" v-model="createForm.shop_id"
:data="shopTreeData" :options="shopCascadeOptions"
:props="shopCascadeProps"
placeholder="请选择店铺" placeholder="请选择店铺"
filterable filterable
clearable clearable
check-strictly
:render-after-expand="false"
:props="{
label: 'shop_name',
value: 'id',
children: 'children'
}"
style="width: 100%" style="width: 100%"
@change="handleShopChange"
/> />
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
@@ -154,7 +154,7 @@
import { h } from 'vue' import { h } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { AgentRechargeService, ShopService } from '@/api/modules' import { AgentRechargeService, ShopService } from '@/api/modules'
import { ElMessage, ElTag, ElTreeSelect, ElButton } from 'element-plus' import { ElMessage, ElTag, ElTreeSelect, ElButton, ElCascader } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import type { import type {
AgentRecharge, AgentRecharge,
@@ -201,6 +201,37 @@
const shopOptions = ref<any[]>([]) const shopOptions = ref<any[]>([])
const shopTreeData = ref<ShopResponse[]>([]) const shopTreeData = ref<ShopResponse[]>([])
// 店铺级联选择相关
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
lazy: true,
checkStrictly: true,
lazyLoad: async (node: any, resolve: any) => {
const { level, value } = node
const parentId = level === 0 ? undefined : value
try {
const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else {
resolve([])
}
} catch (error) {
console.error('加载店铺级联数据失败:', error)
resolve([])
}
},
value: 'value',
label: 'label',
children: 'children'
}
// 搜索表单配置 // 搜索表单配置
const searchFormItems: SearchFormItem[] = [ const searchFormItems: SearchFormItem[] = [
{ {
@@ -427,26 +458,38 @@
return tree return tree
} }
// 加载店铺列表 // 加载店铺列表 - 改用级联查询
const loadShops = async () => { const loadShops = async () => {
try { try {
const params: any = { // 加载平铺列表用于搜索(保留原有逻辑)
page: 1, const res1 = await ShopService.getShops({ page: 1, page_size: 9999 })
page_size: 9999 // 获取所有数据用于构建树形结构 if (res1.code === 0) {
shopOptions.value = res1.data.items || []
} }
const res = await ShopService.getShops(params)
if (res.code === 0) { // 加载顶级店铺用于级联选择
const items = res.data.items || [] const res2 = await ShopService.getShopsCascade({ parent_id: undefined })
// 保留平铺列表用于搜索 if (res2.code === 0) {
shopOptions.value = items shopCascadeOptions.value = (res2.data || []).map((item: any) => ({
// 构建树形数据用于创建对话框 value: item.id,
shopTreeData.value = buildTreeData(items) label: item.shop_name,
leaf: !item.has_children
}))
} }
} catch (error) { } catch (error) {
console.error('Load shops failed:', error) console.error('Load shops failed:', error)
} }
} }
// 处理店铺选择变化
const handleShopChange = (value: any) => {
if (Array.isArray(value) && value.length > 0) {
createForm.shop_id = value[value.length - 1]
} else {
createForm.shop_id = null
}
}
// 获取充值订单列表 // 获取充值订单列表
const getTableData = async () => { const getTableData = async () => {
loading.value = true loading.value = true
@@ -590,12 +633,24 @@
}) })
} }
// 查看详情
const handleViewDetail = (row: AgentRecharge) => {
router.push({
name: 'AgentRechargeDetailRoute',
params: { id: row.id }
})
}
// 获取操作按钮 // 获取操作按钮
const getActions = (row: AgentRecharge) => { const getActions = (row: AgentRecharge) => {
const actions: any[] = [] const actions: any[] = []
// 待支付且线下转账的订单可以确认支付 // 待支付且线下转账的订单可以确认支付
if (row.status === 1 && row.payment_method === 'offline' && hasAuth('agent_recharge:confirm_payment')) { if (
row.status === 1 &&
row.payment_method === 'offline' &&
hasAuth('agent_recharge:confirm_payment')
) {
actions.push({ actions.push({
label: '确认支付', label: '确认支付',
handler: () => handleShowConfirmPay(row), handler: () => handleShowConfirmPay(row),

View File

@@ -51,7 +51,6 @@
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ElCard, ElButton, ElIcon, ElMessage, ElTable, ElTableColumn, ElTag } from 'element-plus' import { ElCard, ElButton, ElIcon, ElMessage, ElTable, ElTableColumn, ElTag } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue' import { ArrowLeft, Loading } from '@element-plus/icons-vue'
import { useI18n } from 'vue-i18n'
import DetailPage from '@/components/common/DetailPage.vue' import DetailPage from '@/components/common/DetailPage.vue'
import type { DetailSection } from '@/components/common/DetailPage.vue' import type { DetailSection } from '@/components/common/DetailPage.vue'
import { OrderService } from '@/api/modules' import { OrderService } from '@/api/modules'
@@ -60,7 +59,6 @@
defineOptions({ name: 'OrderDetail' }) defineOptions({ name: 'OrderDetail' })
const { t } = useI18n()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -74,24 +72,20 @@
// 获取订单类型文本 // 获取订单类型文本
const getOrderTypeText = (type: string): string => { const getOrderTypeText = (type: string): string => {
return type === 'single_card' return type === 'single_card' ? '单卡购买' : '设备购买'
? t('orderManagement.orderType.singleCard')
: t('orderManagement.orderType.device')
} }
// 获取买家类型文本 // 获取买家类型文本
const getBuyerTypeText = (type: string): string => { const getBuyerTypeText = (type: string): string => {
return type === 'personal' return type === 'personal' ? '个人客户' : '代理商'
? t('orderManagement.buyerType.personal')
: t('orderManagement.buyerType.agent')
} }
// 获取支付方式文本 // 获取支付方式文本
const getPaymentMethodText = (method: string): string => { const getPaymentMethodText = (method: string): string => {
const methodMap: Record<string, string> = { const methodMap: Record<string, string> = {
wallet: t('orderManagement.paymentMethod.wallet'), wallet: '钱包支付',
wechat: t('orderManagement.paymentMethod.wechat'), wechat: '微信支付',
alipay: t('orderManagement.paymentMethod.alipay') alipay: '支付宝支付'
} }
return methodMap[method] || method return methodMap[method] || method
} }
@@ -99,9 +93,9 @@
// 获取佣金状态文本 // 获取佣金状态文本
const getCommissionStatusText = (status: number): string => { const getCommissionStatusText = (status: number): string => {
const statusMap: Record<number, string> = { const statusMap: Record<number, string> = {
0: t('orderManagement.commissionStatus.notApplicable'), 0: '不适用',
1: t('orderManagement.commissionStatus.pending'), 1: '待结算',
2: t('orderManagement.commissionStatus.settled') 2: '已结算'
} }
return statusMap[status] || '-' return statusMap[status] || '-'
} }
@@ -122,17 +116,17 @@
{ {
title: '基本信息', title: '基本信息',
fields: [ fields: [
{ label: t('orderManagement.table.orderNo'), prop: 'order_no' }, { label: '订单号', prop: 'order_no' },
{ {
label: t('orderManagement.table.orderType'), label: '订单类型',
formatter: (_, data) => getOrderTypeText(data.order_type) formatter: (_, data) => getOrderTypeText(data.order_type)
}, },
{ {
label: t('orderManagement.table.paymentStatus'), label: '支付状态',
formatter: (_, data) => data.payment_status_text || '-' formatter: (_, data) => data.payment_status_text || '-'
}, },
{ {
label: t('orderManagement.table.totalAmount'), label: '订单金额',
prop: 'total_amount', prop: 'total_amount',
formatter: (value) => formatCurrency(value) formatter: (value) => formatCurrency(value)
}, },
@@ -148,7 +142,7 @@
formatter: (value) => value || '-' formatter: (value) => value || '-'
}, },
{ {
label: t('orderManagement.table.buyerType'), label: '买家类型',
formatter: (_, data) => (data.buyer_type ? getBuyerTypeText(data.buyer_type) : '-') formatter: (_, data) => (data.buyer_type ? getBuyerTypeText(data.buyer_type) : '-')
}, },
{ {
@@ -198,7 +192,7 @@
formatter: (value) => (value ? formatDateTime(value) : '-') formatter: (value) => (value ? formatDateTime(value) : '-')
}, },
{ {
label: t('orderManagement.table.commissionStatus'), label: '佣金状态',
formatter: (_, data) => getCommissionStatusText(data.commission_status) formatter: (_, data) => getCommissionStatusText(data.commission_status)
}, },
{ {
@@ -206,12 +200,12 @@
prop: 'commission_config_version' prop: 'commission_config_version'
}, },
{ {
label: t('orderManagement.table.createdAt'), label: '创建时间',
prop: 'created_at', prop: 'created_at',
formatter: (value) => formatDateTime(value) formatter: (value) => formatDateTime(value)
}, },
{ {
label: t('orderManagement.table.updatedAt'), label: '更新时间',
prop: 'updated_at', prop: 'updated_at',
formatter: (value) => formatDateTime(value) formatter: (value) => formatDateTime(value)
} }

View File

@@ -176,6 +176,26 @@
</template> </template>
</div> </div>
</ElFormItem> </ElFormItem>
<ElFormItem
v-if="createForm.payment_method === 'offline'"
label="支付凭证"
prop="payment_voucher_key"
>
<ElUpload
ref="uploadRef"
:auto-upload="false"
:on-change="handleVoucherFileChange"
:on-remove="handleRemoveVoucher"
accept="image/*"
list-type="picture-card"
:limit="1"
>
<el-icon><Plus /></el-icon>
<template #tip>
<div class="el-upload__tip">支持 jpgpng 格式文件大小不超过 5MB</div>
</template>
</ElUpload>
</ElFormItem>
</ElForm> </ElForm>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
@@ -265,9 +285,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { h } from 'vue' import { h } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { OrderService, CardService, DeviceService, PackageManageService } from '@/api/modules' import { OrderService, CardService, DeviceService, PackageManageService, StorageService } from '@/api/modules'
import { ElMessage, ElMessageBox, ElTag } from 'element-plus' import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus' import { Plus } from '@element-plus/icons-vue'
import type { FormInstance, FormRules, UploadInstance, UploadFile } from 'element-plus'
import type { import type {
Order, Order,
OrderQueryParams, OrderQueryParams,
@@ -408,29 +429,104 @@
] ]
const createFormRef = ref<FormInstance>() const createFormRef = ref<FormInstance>()
const uploadRef = ref<UploadInstance>()
const createRules = reactive<FormRules>({ // 处理支付凭证文件选择
order_type: [ const handleVoucherFileChange = async (uploadFile: UploadFile) => {
{ const file = uploadFile.raw
required: true, if (!file) return
message: '请选择订单类型',
trigger: 'change' // 验证文件类型
const isImage = file.type.startsWith('image/')
if (!isImage) {
ElMessage.error('只能上传图片文件!')
uploadRef.value?.clearFiles()
return
}
// 验证文件大小
const isLt5M = file.size / 1024 / 1024 < 5
if (!isLt5M) {
ElMessage.error('图片大小不能超过 5MB!')
uploadRef.value?.clearFiles()
return
}
// 开始上传
try {
ElMessage.info('正在上传支付凭证...')
// 1. 获取上传 URL
const uploadUrlRes = await StorageService.getUploadUrl({
file_name: file.name,
content_type: file.type,
purpose: 'attachment'
})
if (uploadUrlRes.code !== 0) {
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
uploadRef.value?.clearFiles()
return
} }
],
package_ids: [ const { upload_url, file_key } = uploadUrlRes.data
{
required: true, // 2. 上传文件到对象存储
message: '请选择套餐', await StorageService.uploadFile(upload_url, file, file.type)
trigger: 'change'
} // 3. 保存 file_key
], createForm.payment_voucher_key = file_key
payment_method: [ ElMessage.success('上传成功')
{ } catch (error: any) {
required: true, console.error('上传失败:', error)
message: '请选择支付方式', ElMessage.error(error.message || '上传失败,请重试')
trigger: 'change' createForm.payment_voucher_key = undefined
} uploadRef.value?.clearFiles()
] }
}
// 删除支付凭证
const handleRemoveVoucher = () => {
createForm.payment_voucher_key = undefined
}
const createRules = computed<FormRules>(() => {
const baseRules: FormRules = {
order_type: [
{
required: true,
message: '请选择订单类型',
trigger: 'change'
}
],
package_ids: [
{
required: true,
message: '请选择套餐',
trigger: 'change'
}
],
payment_method: [
{
required: true,
message: '请选择支付方式',
trigger: 'change'
}
]
}
// 线下支付时,支付凭证为必填
if (createForm.payment_method === 'offline') {
baseRules.payment_voucher_key = [
{
required: true,
message: '请上传支付凭证',
trigger: 'change'
}
]
}
return baseRules
}) })
const createForm = reactive<CreateOrderRequest>({ const createForm = reactive<CreateOrderRequest>({
@@ -438,7 +534,8 @@
package_ids: [], package_ids: [],
iot_card_id: null, iot_card_id: null,
device_id: null, device_id: null,
payment_method: 'wallet' // 默认使用钱包支付 payment_method: 'wallet', // 默认使用钱包支付
payment_voucher_key: undefined // 线下支付凭证
}) })
const orderList = ref<Order[]>([]) const orderList = ref<Order[]>([])
@@ -884,6 +981,10 @@
createForm.iot_card_id = null createForm.iot_card_id = null
createForm.device_id = null createForm.device_id = null
createForm.payment_method = 'wallet' createForm.payment_method = 'wallet'
createForm.payment_voucher_key = undefined
// 清空上传文件列表
uploadRef.value?.clearFiles()
// 清空套餐、IoT卡和设备搜索结果 // 清空套餐、IoT卡和设备搜索结果
packageOptions.value = [] packageOptions.value = []
@@ -964,6 +1065,11 @@
payment_method: createForm.payment_method payment_method: createForm.payment_method
} }
// 线下支付时,添加支付凭证
if (createForm.payment_method === 'offline' && createForm.payment_voucher_key) {
data.payment_voucher_key = createForm.payment_voucher_key
}
await OrderService.createOrder(data) await OrderService.createOrder(data)
// 根据支付方式显示不同的成功消息 // 根据支付方式显示不同的成功消息

View File

@@ -690,15 +690,17 @@
label: '虚流量比例', label: '虚流量比例',
width: 120, width: 120,
formatter: (row: PackageResponse) => { formatter: (row: PackageResponse) => {
// 如果启用虚流量且流量大于0计算比 // 如果启用虚流量且流量大于0计算虚量百分
const virtualData = row.virtual_data_mb ?? 0 const virtualData = row.virtual_data_mb ?? 0
const realData = row.real_data_mb ?? 0 const realData = row.real_data_mb ?? 0
if (row.enable_virtual_data && virtualData > 0) { if (row.enable_virtual_data && realData > 0 && virtualData > 0) {
const ratio = realData / virtualData // 虚量百分比 = (1 - 虚流量/真流量) * 100%
return ratio.toFixed(2) // 例如真流量100G虚流量70G则虚量百分比 = (1 - 70/100) * 100% = 30%
const ratio = (1 - virtualData / realData) * 100
return `${ratio.toFixed(2)}%`
} }
// 否则返回 1.0 // 否则返回 0%
return '1.00' return '0%'
} }
}, },
{ {

View File

@@ -174,13 +174,18 @@
<ElFormItem label="成本价(元)" prop="cost_price_yuan"> <ElFormItem label="成本价(元)" prop="cost_price_yuan">
<ElInputNumber <ElInputNumber
v-model="packageForm.cost_price_yuan" v-model="packageForm.cost_price_yuan"
:min="0" :min="packageForm.original_cost_price || 0"
:max="packageForm.original_cost_price ? packageForm.original_cost_price * 1.5 : undefined"
:precision="2" :precision="2"
:step="0.01" :step="0.01"
:controls="false" :controls="false"
style="width: 100%" style="width: 100%"
placeholder="请输入成本价" placeholder="请输入成本价"
/> />
<div v-if="packageForm.original_cost_price" class="form-tip">
原价: ¥{{ packageForm.original_cost_price.toFixed(2) }},
最高: ¥{{ (packageForm.original_cost_price * 1.5).toFixed(2) }} (50%溢价)
</div>
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
@@ -245,17 +250,16 @@
<ElRow :gutter="20" v-if="form.series_id"> <ElRow :gutter="20" v-if="form.series_id">
<ElCol :span="24"> <ElCol :span="24">
<ElFormItem label="选择店铺" prop="shop_id"> <ElFormItem label="选择店铺" prop="shop_id">
<ElTreeSelect <ElCascader
v-model="form.shop_id" v-model="form.shop_id"
:data="shopTreeData" :options="shopCascadeOptions"
:props="{ label: 'shop_name', value: 'id', children: 'children' }" :props="shopCascadeProps"
placeholder="请选择店铺" placeholder="请选择或搜索店铺"
style="width: 100%" style="width: 100%"
filterable filterable
clearable clearable
:loading="shopLoading" :loading="shopLoading"
check-strictly @change="handleShopChange"
:render-after-expand="false"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
@@ -308,6 +312,7 @@
<ElInputNumber <ElInputNumber
v-model="pkg.cost_price" v-model="pkg.cost_price"
:min="pkg.original_cost_price || 0" :min="pkg.original_cost_price || 0"
:max="pkg.original_cost_price ? pkg.original_cost_price * 1.5 : undefined"
:precision="2" :precision="2"
:step="0.01" :step="0.01"
:controls="false" :controls="false"
@@ -315,7 +320,7 @@
style="width: 150px" style="width: 150px"
/> />
<span v-if="pkg.original_cost_price" class="min-cost-hint"> <span v-if="pkg.original_cost_price" class="min-cost-hint">
(成本: ¥{{ pkg.original_cost_price.toFixed(2) }}) (: ¥{{ pkg.original_cost_price.toFixed(2) }}, 最高: ¥{{ (pkg.original_cost_price * 1.5).toFixed(2) }})
</span> </span>
</div> </div>
<ElButton type="danger" size="small" @click="removePackage(index)" <ElButton type="danger" size="small" @click="removePackage(index)"
@@ -324,7 +329,7 @@
</div> </div>
</div> </div>
<div class="form-tip" <div class="form-tip"
>设置每个套餐的成本价单位不能低于套餐原始成本</div >设置每个套餐的成本价单位不能低于原价不能高于原价的50%</div
> >
</ElFormItem> </ElFormItem>
</template> </template>
@@ -333,12 +338,13 @@
<!-- 第二步佣金和强制充值配置 --> <!-- 第二步佣金和强制充值配置 -->
<div v-show="currentStep === 1"> <div v-show="currentStep === 1">
<!-- 一次性佣金配置 --> <!-- 一次性佣金配置 -->
<div class="form-section-title"> <template v-if="form.enable_one_time_commission">
<span class="title-text">一次性佣金配置</span> <div class="form-section-title">
</div> <span class="title-text">一次性佣金配置</span>
</div>
<!-- 佣金类型和金额 - 2列布局 --> <!-- 佣金类型和金额 - 2列布局 -->
<ElRow :gutter="20"> <ElRow :gutter="20">
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="佣金类型"> <ElFormItem label="佣金类型">
<div class="commission-type-display"> <div class="commission-type-display">
@@ -432,6 +438,7 @@
</div> </div>
</ElFormItem> </ElFormItem>
</template> </template>
</template>
<!-- 强制充值配置 --> <!-- 强制充值配置 -->
<div class="form-section-title"> <div class="form-section-title">
@@ -497,12 +504,13 @@
</div> </div>
<!-- 一次性佣金配置 --> <!-- 一次性佣金配置 -->
<div class="form-section-title"> <template v-if="form.enable_one_time_commission">
<span class="title-text">一次性佣金配置</span> <div class="form-section-title">
</div> <span class="title-text">一次性佣金配置</span>
</div>
<!-- 佣金类型和金额 - 2列布局 --> <!-- 佣金类型和金额 - 2列布局 -->
<ElRow :gutter="20" v-if="form.series_id"> <ElRow :gutter="20" v-if="form.series_id">
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="佣金类型"> <ElFormItem label="佣金类型">
<div class="commission-type-display"> <div class="commission-type-display">
@@ -596,6 +604,7 @@
</div> </div>
</ElFormItem> </ElFormItem>
</template> </template>
</template>
<!-- 强制充值配置 --> <!-- 强制充值配置 -->
<div class="form-section-title"> <div class="form-section-title">
@@ -808,6 +817,7 @@
package_name?: string package_name?: string
package_code?: string package_code?: string
cost_price_yuan: number cost_price_yuan: number
original_cost_price?: number // 原始成本价(元)
}>({ }>({
cost_price_yuan: 0 cost_price_yuan: 0
}) })
@@ -819,7 +829,22 @@
], ],
cost_price_yuan: [ cost_price_yuan: [
{ required: true, message: '请输入成本价', trigger: 'blur' }, { required: true, message: '请输入成本价', trigger: 'blur' },
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' } {
validator: (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入成本价'))
} else if (value < 0) {
callback(new Error('成本价不能小于0'))
} else if (packageForm.value.original_cost_price && value < packageForm.value.original_cost_price) {
callback(new Error(`成本价不能低于原价 ¥${packageForm.value.original_cost_price.toFixed(2)}`))
} else if (packageForm.value.original_cost_price && value > packageForm.value.original_cost_price * 1.5) {
callback(new Error(`成本价不能高于原价的50%溢价(最高:¥${(packageForm.value.original_cost_price * 1.5).toFixed(2)}`))
} else {
callback()
}
},
trigger: 'blur'
}
] ]
})) }))
@@ -832,6 +857,37 @@
const searchShopOptions = ref<ShopResponse[]>([]) const searchShopOptions = ref<ShopResponse[]>([])
const searchAllocatorShopOptions = ref<ShopResponse[]>([]) const searchAllocatorShopOptions = ref<ShopResponse[]>([])
// 店铺级联选择相关
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
lazy: true,
checkStrictly: true, // 允许选择任意一级
lazyLoad: async (node: any, resolve: any) => {
const { level, value } = node
const parentId = level === 0 ? undefined : value
try {
const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else {
resolve([])
}
} catch (error) {
console.error('加载店铺级联数据失败:', error)
resolve([])
}
},
value: 'value',
label: 'label',
children: 'children'
}
// 搜索表单初始值 // 搜索表单初始值
const initialSearchState = { const initialSearchState = {
shop_id: undefined as number | undefined, shop_id: undefined as number | undefined,
@@ -944,8 +1000,9 @@
series_name: '', series_name: '',
shop_name: '', shop_name: '',
allocator_shop_name: '', allocator_shop_name: '',
enable_one_time_commission: false, // 是否启用一次性佣金(从系列继承)
commission_type: 'fixed' as 'fixed' | 'tiered', // 佣金类型 commission_type: 'fixed' as 'fixed' | 'tiered', // 佣金类型
one_time_commission_amount: 0, // 固定佣金金额(元) one_time_commission_amount: undefined, // 固定佣金金额(元)
series_max_commission_amount: 0, // 系列最大佣金金额(元) series_max_commission_amount: 0, // 系列最大佣金金额(元)
commission_tiers: [] as Array<{ commission_tiers: [] as Array<{
operator?: '>=' | '>' | '<=' | '<' // 比较运算符 operator?: '>=' | '>' | '<=' | '<' // 比较运算符
@@ -968,59 +1025,62 @@
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }] shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }]
} }
// 根据佣金类型添加验证规则 // 只有启用一次性佣金时才添加佣金验证规则
if (form.commission_type === 'fixed') { if (form.enable_one_time_commission) {
baseRules.one_time_commission_amount = [ // 根据佣金类型添加验证规则
{ required: true, message: '请输入固定佣金金额', trigger: 'blur' }, if (form.commission_type === 'fixed') {
{ baseRules.one_time_commission_amount = [
validator: (rule, value, callback) => { { required: true, message: '请输入固定佣金金额', trigger: 'blur' },
if (value === undefined || value === null || value === '') { {
callback(new Error('请输入固定佣金金额')) validator: (rule, value, callback) => {
} else if (value < 0) { if (value === undefined || value === null || value === '') {
callback(new Error('佣金金额不能小于0')) callback(new Error('请输入固定佣金金额'))
} else if ( } else if (value < 0) {
form.series_max_commission_amount > 0 && callback(new Error('佣金金额不能小于0'))
value > form.series_max_commission_amount } else if (
) { form.series_max_commission_amount > 0 &&
callback( value > form.series_max_commission_amount
new Error( ) {
`佣金金额不能超过该系列最大值 ¥${form.series_max_commission_amount.toFixed(2)}`
)
)
} else {
callback()
}
},
trigger: 'blur'
}
]
} else if (form.commission_type === 'tiered') {
baseRules.commission_tiers = [
{
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少添加一个梯度配置'))
return
}
// 验证每个梯度的佣金金额不超过最大值
for (let i = 0; i < value.length; i++) {
const tier = value[i]
if (tier.max_amount && tier.amount > tier.max_amount) {
callback( callback(
new Error( new Error(
`${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}` `佣金金额不能超过系列最大值 ¥${form.series_max_commission_amount.toFixed(2)}`
) )
) )
} else {
callback()
}
},
trigger: 'blur'
}
]
} else if (form.commission_type === 'tiered') {
baseRules.commission_tiers = [
{
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少添加一个梯度配置'))
return return
} }
}
callback() // 验证每个梯度的佣金金额不超过最大值
}, for (let i = 0; i < value.length; i++) {
trigger: 'change' const tier = value[i]
} if (tier.max_amount && tier.amount > tier.max_amount) {
] callback(
new Error(
`${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}`
)
)
return
}
}
callback()
},
trigger: 'change'
}
]
}
} }
// 如果启用了强制充值,添加验证规则 // 如果启用了强制充值,添加验证规则
@@ -1207,9 +1267,14 @@
return tree return tree
} }
// 店铺树节点过滤方法
const filterShopNode = (value: string, data: any) => {
if (!value) return true
return data.shop_name.toLowerCase().includes(value.toLowerCase())
}
onMounted(() => { onMounted(() => {
loadSeriesOptions() loadSeriesOptions()
loadShopOptions()
loadSearchSeriesOptions() loadSearchSeriesOptions()
loadSearchShopOptions() loadSearchShopOptions()
loadSearchAllocatorShopOptions() loadSearchAllocatorShopOptions()
@@ -1322,6 +1387,9 @@
// 设置系列名称 // 设置系列名称
form.series_name = selectedSeries.series_name form.series_name = selectedSeries.series_name
// 获取系列是否启用一次性佣金
form.enable_one_time_commission = selectedSeries.enable_one_time_commission || false
// 从系列配置中获取佣金类型 // 从系列配置中获取佣金类型
const commissionConfig = selectedSeries.one_time_commission_config const commissionConfig = selectedSeries.one_time_commission_config
if (commissionConfig && commissionConfig.commission_type) { if (commissionConfig && commissionConfig.commission_type) {
@@ -1329,14 +1397,14 @@
// 根据佣金类型初始化对应字段 // 根据佣金类型初始化对应字段
if (commissionConfig.commission_type === 'fixed') { if (commissionConfig.commission_type === 'fixed') {
form.one_time_commission_amount = 0 form.one_time_commission_amount = undefined
// 设置系列最大佣金金额(从分转换为元) // 设置系列最大佣金金额(从分转换为元)
form.series_max_commission_amount = commissionConfig.commission_amount form.series_max_commission_amount = commissionConfig.commission_amount
? commissionConfig.commission_amount / 100 ? commissionConfig.commission_amount / 100
: 0 : 0
form.commission_tiers = [{ threshold: 0, amount: 0 }] form.commission_tiers = [{ threshold: 0, amount: 0 }]
} else if (commissionConfig.commission_type === 'tiered') { } else if (commissionConfig.commission_type === 'tiered') {
form.one_time_commission_amount = 0 form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0 form.series_max_commission_amount = 0
// 梯度配置从系列继承,包含完整字段,佣金金额也默认继承 // 梯度配置从系列继承,包含完整字段,佣金金额也默认继承
if (commissionConfig.tiers && commissionConfig.tiers.length > 0) { if (commissionConfig.tiers && commissionConfig.tiers.length > 0) {
@@ -1362,7 +1430,7 @@
} else { } else {
// 默认固定佣金 // 默认固定佣金
form.commission_type = 'fixed' form.commission_type = 'fixed'
form.one_time_commission_amount = 0 form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0 form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0 form.series_force_recharge_amount = 0
} }
@@ -1374,7 +1442,7 @@
// 未选择系列,重置佣金类型 // 未选择系列,重置佣金类型
form.series_name = '' form.series_name = ''
form.commission_type = 'fixed' form.commission_type = 'fixed'
form.one_time_commission_amount = 0 form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0 form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0 form.series_force_recharge_amount = 0
form.commission_tiers = [] form.commission_tiers = []
@@ -1394,6 +1462,22 @@
} }
) )
// 监听套餐选择,自动设置原始成本价和成本价
watch(
() => packageForm.value.package_id,
(packageId) => {
if (packageDialogType.value === 'add' && packageId) {
// 从 availablePackages 中查找选中的套餐
const selectedPackage = availablePackages.value.find((p) => p.id === packageId)
if (selectedPackage) {
const originalCostPrice = selectedPackage.cost_price ? selectedPackage.cost_price / 100 : 0
packageForm.value.original_cost_price = originalCostPrice
packageForm.value.cost_price_yuan = originalCostPrice
}
}
}
)
// 加载系列选项(用于新增对话框,默认加载10条 // 加载系列选项(用于新增对话框,默认加载10条
const loadSeriesOptions = async (seriesName?: string) => { const loadSeriesOptions = async (seriesName?: string) => {
seriesLoading.value = true seriesLoading.value = true
@@ -1417,26 +1501,6 @@
} }
} }
// 加载店铺选项(用于新增对话框,加载所有店铺并构建树形结构)
const loadShopOptions = async () => {
shopLoading.value = true
try {
// 加载所有店铺,不分页
const res = await ShopService.getShops({
page: 1,
page_size: 10000 // 使用较大的值获取所有店铺
})
if (res.code === 0) {
shopOptions.value = res.data.items
// 构建树形结构数据
shopTreeData.value = buildTreeData(shopOptions.value)
}
} catch (error) {
console.error('加载店铺选项失败:', error)
} finally {
shopLoading.value = false
}
}
// 加载搜索栏系列选项(默认加载10条) // 加载搜索栏系列选项(默认加载10条)
const loadSearchSeriesOptions = async () => { const loadSearchSeriesOptions = async () => {
@@ -1487,6 +1551,35 @@
} }
} }
// 加载店铺列表(用于新增对话框) - 改用级联查询
const loadShopListForDialog = async () => {
shopLoading.value = true
try {
const res = await ShopService.getShopsCascade({ parent_id: undefined })
if (res.code === 0) {
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
}
} catch (error) {
console.error('加载店铺列表失败:', error)
} finally {
shopLoading.value = false
}
}
// 处理店铺选择变化
const handleShopChange = (value: any) => {
// 级联选择器返回的是数组,取最后一个值作为 shop_id
if (Array.isArray(value) && value.length > 0) {
form.shop_id = value[value.length - 1]
} else {
form.shop_id = undefined
}
}
// 搜索系列(用于搜索栏) // 搜索系列(用于搜索栏)
const handleSearchSeries = async (query: string) => { const handleSearchSeries = async (query: string) => {
if (!query) { if (!query) {
@@ -1608,6 +1701,9 @@
dialogVisible.value = true dialogVisible.value = true
dialogType.value = type dialogType.value = type
// 每次打开对话框时重新加载店铺列表,确保获取最新的店铺数据
await loadShopListForDialog()
if (type === 'edit' && row) { if (type === 'edit' && row) {
// 编辑模式:先调用详情接口获取完整数据 // 编辑模式:先调用详情接口获取完整数据
loading.value = true loading.value = true
@@ -1628,9 +1724,13 @@
// 获取系列配置信息 // 获取系列配置信息
const selectedSeries = seriesOptions.value.find((s) => s.id === detail.series_id) const selectedSeries = seriesOptions.value.find((s) => s.id === detail.series_id)
// 设置是否启用一次性佣金
form.enable_one_time_commission = selectedSeries?.enable_one_time_commission || false
// 设置佣金配置 // 设置佣金配置
if (detail.commission_type === 'fixed') { if (detail.commission_type === 'fixed') {
form.one_time_commission_amount = detail.one_time_commission_amount / 100 const amount = detail.one_time_commission_amount / 100
form.one_time_commission_amount = amount > 0 ? amount : undefined
// 设置系列最大佣金金额 // 设置系列最大佣金金额
if (selectedSeries?.one_time_commission_config?.commission_amount) { if (selectedSeries?.one_time_commission_config?.commission_amount) {
form.series_max_commission_amount = form.series_max_commission_amount =
@@ -1711,8 +1811,9 @@
form.series_name = '' form.series_name = ''
form.shop_name = '' form.shop_name = ''
form.allocator_shop_name = '' form.allocator_shop_name = ''
form.enable_one_time_commission = false
form.commission_type = 'fixed' form.commission_type = 'fixed'
form.one_time_commission_amount = 0 form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0 form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0 form.series_force_recharge_amount = 0
form.commission_tiers = [] form.commission_tiers = []
@@ -1742,8 +1843,9 @@
form.series_name = '' form.series_name = ''
form.shop_name = '' form.shop_name = ''
form.allocator_shop_name = '' form.allocator_shop_name = ''
form.enable_one_time_commission = false
form.commission_type = 'fixed' form.commission_type = 'fixed'
form.one_time_commission_amount = 0 form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0 form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0 form.series_force_recharge_amount = 0
form.commission_tiers = [] form.commission_tiers = []
@@ -1803,17 +1905,20 @@
// 构建请求数据 // 构建请求数据
const data: any = {} const data: any = {}
// 根据佣金类型携带不同参数 // 只有启用一次性佣金时才传递佣金相关参数
if (form.commission_type === 'fixed') { if (form.enable_one_time_commission) {
// 固定模式:携带 one_time_commission_amount将元转换为分 // 根据佣金类型携带不同参数
data.one_time_commission_amount = Math.round(form.one_time_commission_amount * 100) if (form.commission_type === 'fixed') {
} else if (form.commission_type === 'tiered') { // 固定模式:携带 one_time_commission_amount将元转换为分
// 梯度模式:携带 commission_tiers将元转换为分 data.one_time_commission_amount = Math.round((form.one_time_commission_amount || 0) * 100)
// 注意:请求时不传 operatoroperator 是响应中从 PackageSeries 合并过来的 } else if (form.commission_type === 'tiered') {
data.commission_tiers = form.commission_tiers.map((tier: CommissionTier) => ({ // 梯度模式:携带 commission_tiers(将元转换为分)
threshold: tier.threshold, // 注意:请求时不传 operatoroperator 是响应中从 PackageSeries 合并过来的
amount: Math.round(tier.amount * 100) // 将元转换为分 data.commission_tiers = form.commission_tiers.map((tier: CommissionTier) => ({
})) threshold: tier.threshold,
amount: Math.round(tier.amount * 100) // 将元转换为分
}))
}
} }
// 强制充值配置(无论开关状态都要传递) // 强制充值配置(无论开关状态都要传递)
@@ -1828,9 +1933,30 @@
// 可选:套餐配置(将元转换为分) // 可选:套餐配置(将元转换为分)
if (form.packages.length > 0) { if (form.packages.length > 0) {
// 验证成本价是否在允许范围内
for (const pkg of form.packages) {
const costPrice = pkg.cost_price ?? 0
const originalCostPrice = pkg.original_cost_price ?? 0
if (originalCostPrice > 0 && costPrice > originalCostPrice * 1.5) {
ElMessage.error(
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能高于原价的50%溢价(最高:¥${(originalCostPrice * 1.5).toFixed(2)}`
)
submitLoading.value = false
return
}
if (originalCostPrice > 0 && costPrice < originalCostPrice) {
ElMessage.error(
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能低于原价(¥${originalCostPrice.toFixed(2)}`
)
submitLoading.value = false
return
}
}
data.packages = form.packages.map((pkg: any) => ({ data.packages = form.packages.map((pkg: any) => ({
package_id: pkg.package_id, package_id: pkg.package_id,
cost_price: Math.round(pkg.cost_price * 100) // 将元转换为分 cost_price: Math.round((pkg.cost_price ?? 0) * 100) // 将元转换为分
})) }))
} }
@@ -1912,11 +2038,17 @@
// 显示编辑套餐对话框 // 显示编辑套餐对话框
const showEditPackageDialog = (row: GrantPackageInfo) => { const showEditPackageDialog = (row: GrantPackageInfo) => {
packageDialogType.value = 'edit' packageDialogType.value = 'edit'
// 从 packageOptions 中查找原始成本价
const pkgOption = packageOptions.value.find((p) => p.id === row.package_id)
const originalCostPrice = pkgOption?.cost_price ? pkgOption.cost_price / 100 : 0
packageForm.value = { packageForm.value = {
package_id: row.package_id, package_id: row.package_id,
package_name: row.package_name, package_name: row.package_name,
package_code: row.package_code, package_code: row.package_code,
cost_price_yuan: row.cost_price / 100 cost_price_yuan: row.cost_price / 100,
original_cost_price: originalCostPrice
} }
packageDialogVisible.value = true packageDialogVisible.value = true

View File

@@ -5,6 +5,7 @@
<ArtSearchBar <ArtSearchBar
v-model:filter="searchForm" v-model:filter="searchForm"
:items="searchFormItems" :items="searchFormItems"
:show-expand="false"
@reset="handleReset" @reset="handleReset"
@search="handleSearch" @search="handleSearch"
></ArtSearchBar> ></ArtSearchBar>
@@ -31,9 +32,6 @@
:pageSize="pagination.pageSize" :pageSize="pagination.pageSize"
:total="pagination.total" :total="pagination.total"
:marginTop="10" :marginTop="10"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="false"
:pagination="false"
:actions="getActions" :actions="getActions"
:actionsWidth="160" :actionsWidth="160"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@@ -76,20 +74,15 @@
<ElRow :gutter="20" v-if="dialogType === 'add'"> <ElRow :gutter="20" v-if="dialogType === 'add'">
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="上级店铺" prop="parent_id"> <ElFormItem label="上级店铺" prop="parent_id">
<ElTreeSelect <ElCascader
v-model="formData.parent_id" v-model="formData.parent_id"
:data="parentShopTreeData" :options="parentShopCascadeOptions"
:props="cascadeProps"
placeholder="一级店铺可不选" placeholder="一级店铺可不选"
filterable filterable
clearable clearable
check-strictly
:render-after-expand="false"
:props="{
label: 'shop_name',
value: 'id',
children: 'children'
}"
style="width: 100%" style="width: 100%"
@change="handleParentShopChange"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
@@ -287,7 +280,6 @@
ElSwitch, ElSwitch,
ElSelect, ElSelect,
ElOption, ElOption,
ElTreeSelect,
ElCascader ElCascader
} from 'element-plus' } from 'element-plus'
import { ref, reactive, computed, nextTick, onMounted } from 'vue' import { ref, reactive, computed, nextTick, onMounted } from 'vue'
@@ -312,12 +304,41 @@
const dialogVisible = ref(false) const dialogVisible = ref(false)
const loading = ref(false) const loading = ref(false)
const submitLoading = ref(false) const submitLoading = ref(false)
const parentShopLoading = ref(false)
const parentShopList = ref<ShopResponse[]>([])
const parentShopTreeData = ref<ShopResponse[]>([])
const defaultRoleLoading = ref(false) const defaultRoleLoading = ref(false)
const defaultRoleList = ref<any[]>([]) const defaultRoleList = ref<any[]>([])
// 级联选择相关
const parentShopCascadeOptions = ref<any[]>([])
const cascadeProps = {
lazy: true,
checkStrictly: true, // 允许选择任意一级
lazyLoad: async (node: any, resolve: any) => {
const { level, value } = node
// level 0 表示根节点,不传 parent_idlevel > 0 传 parent_id
const parentId = level === 0 ? undefined : value
try {
const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else {
resolve([])
}
} catch (error) {
console.error('加载店铺级联数据失败:', error)
resolve([])
}
},
value: 'value',
label: 'label',
children: 'children'
}
// 默认角色管理相关状态 // 默认角色管理相关状态
const defaultRolesDialogVisible = ref(false) const defaultRolesDialogVisible = ref(false)
const defaultRolesLoading = ref(false) const defaultRolesLoading = ref(false)
@@ -330,6 +351,7 @@
const initialSearchState = { const initialSearchState = {
shop_name: '', shop_name: '',
shop_code: '', shop_code: '',
parent_shop_name: '',
parent_id: undefined as number | undefined, parent_id: undefined as number | undefined,
level: undefined as number | undefined, level: undefined as number | undefined,
status: undefined as number | undefined status: undefined as number | undefined
@@ -386,24 +408,6 @@
placeholder: '请输入店铺编号' placeholder: '请输入店铺编号'
} }
}, },
{
label: '上级店铺',
prop: 'parent_id',
type: 'tree-select',
config: {
data: parentShopTreeData.value,
clearable: true,
filterable: true,
checkStrictly: true,
renderAfterExpand: false,
props: {
label: 'shop_name',
value: 'id',
children: 'children'
},
placeholder: '请选择上级店铺'
}
},
{ {
label: '状态', label: '状态',
prop: 'status', prop: 'status',
@@ -420,6 +424,7 @@
const columnOptions = [ const columnOptions = [
{ label: '店铺名称', prop: 'shop_name' }, { label: '店铺名称', prop: 'shop_name' },
{ label: '店铺编号', prop: 'shop_code' }, { label: '店铺编号', prop: 'shop_code' },
{ label: '上级店铺', prop: 'parent_shop_name' },
{ label: '所在地区', prop: 'region' }, { label: '所在地区', prop: 'region' },
{ label: '联系人', prop: 'contact_name' }, { label: '联系人', prop: 'contact_name' },
{ label: '联系电话', prop: 'contact_phone' }, { label: '联系电话', prop: 'contact_phone' },
@@ -458,9 +463,6 @@
formData.init_phone = '' formData.init_phone = ''
formData.default_role_id = undefined formData.default_role_id = undefined
} else { } else {
// 新增模式下重新获取上级店铺列表
await loadParentShopList()
formData.id = 0 formData.id = 0
formData.shop_name = '' formData.shop_name = ''
formData.shop_code = '' formData.shop_code = ''
@@ -499,8 +501,8 @@
await ShopService.deleteShop(row.id) await ShopService.deleteShop(row.id)
ElMessage.success('删除成功') ElMessage.success('删除成功')
await getShopList() await getShopList()
// 删除成功后也更新上级店铺列表 // 删除成功后也重新加载顶级店铺
await loadParentShopList() await loadTopLevelShops()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
@@ -524,6 +526,13 @@
minWidth: 160, minWidth: 160,
showOverflowTooltip: true showOverflowTooltip: true
}, },
{
prop: 'parent_shop_name',
label: '上级店铺',
width: 150,
showOverflowTooltip: true,
formatter: (row: ShopResponse) => row.parent_shop_name || '-'
},
{ {
prop: 'region', prop: 'region',
label: '所在地区', label: '所在地区',
@@ -621,7 +630,7 @@
id: 0, id: 0,
shop_name: '', shop_name: '',
shop_code: '', shop_code: '',
parent_id: undefined as number | undefined, parent_id: undefined as number | number[] | undefined, // 支持级联选择器的数组格式
region: [] as string[], // 省市区级联数据 region: [] as string[], // 省市区级联数据
province: '', province: '',
city: '', city: '',
@@ -682,54 +691,55 @@
} }
} }
onMounted(() => { // 处理级联选择变化
getShopList() const handleParentShopChange = (value: any) => {
loadParentShopList() // 级联选择器返回的是数组,取最后一个值作为 parent_id
searchDefaultRoles('') // 加载初始默认角色列表 if (Array.isArray(value) && value.length > 0) {
}) formData.parent_id = value[value.length - 1]
} else {
// 加载上级店铺列表(用于新增对话框和搜索栏,获取所有店铺构建树形结构) formData.parent_id = undefined
const loadParentShopList = async (shopName?: string) => {
parentShopLoading.value = true
try {
const params: any = {
page: 1,
page_size: 9999 // 获取所有数据用于构建树形结构
}
if (shopName) {
params.shop_name = shopName
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
const items = res.data.items || []
parentShopList.value = items
// 构建树形数据
parentShopTreeData.value = buildTreeData(items)
}
} catch (error) {
console.error('获取上级店铺列表失败:', error)
} finally {
parentShopLoading.value = false
} }
} }
// 加载顶级店铺数据(用于级联选择器初始化)
const loadTopLevelShops = async () => {
try {
const res = await ShopService.getShopsCascade({ parent_id: undefined })
if (res.code === 0) {
parentShopCascadeOptions.value = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
}
} catch (error) {
console.error('加载顶级店铺失败:', error)
}
}
onMounted(() => {
getShopList()
loadTopLevelShops() // 加载顶级店铺用于级联选择
searchDefaultRoles('') // 加载初始默认角色列表
})
// 获取店铺列表 // 获取店铺列表
const getShopList = async () => { const getShopList = async () => {
loading.value = true loading.value = true
try { try {
const params = { const params = {
page: 1, page: pagination.currentPage,
page_size: 9999, // 获取所有数据用于构建树形结构 page_size: pagination.pageSize,
shop_name: searchForm.shop_name || undefined, shop_name: searchForm.shop_name || undefined,
shop_code: searchForm.shop_code || undefined, shop_code: searchForm.shop_code || undefined,
parent_shop_name: searchForm.parent_shop_name || undefined,
parent_id: searchForm.parent_id, parent_id: searchForm.parent_id,
level: searchForm.level, level: searchForm.level,
status: searchForm.status status: searchForm.status
} }
const res = await ShopService.getShops(params) const res = await ShopService.getShops(params)
if (res.code === 0) { if (res.code === 0) {
const items = res.data.items || [] tableData.value = res.data.items || []
tableData.value = buildTreeData(items)
pagination.total = res.data.total || 0 pagination.total = res.data.total || 0
} }
} catch (error) { } catch (error) {
@@ -739,33 +749,6 @@
} }
} }
// 构建树形数据
const buildTreeData = (items: ShopResponse[]) => {
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
const tree: ShopResponse[] = []
// 先将所有项放入 map
items.forEach((item) => {
map.set(item.id, { ...item, children: [] })
})
// 构建树形结构
items.forEach((item) => {
const node = map.get(item.id)!
if (item.parent_id && map.has(item.parent_id)) {
// 有父节点,添加到父节点的 children 中
const parent = map.get(item.parent_id)!
if (!parent.children) parent.children = []
parent.children.push(node)
} else {
// 没有父节点或父节点不存在,作为根节点
tree.push(node)
}
})
return tree
}
const handleRefresh = () => { const handleRefresh = () => {
getShopList() getShopList()
} }
@@ -825,8 +808,12 @@
default_role_id: formData.default_role_id default_role_id: formData.default_role_id
} }
// 可选字段 // 可选字段 - parent_id 可能是数组(级联选择器)或数字
if (formData.parent_id) data.parent_id = formData.parent_id if (formData.parent_id) {
data.parent_id = Array.isArray(formData.parent_id)
? formData.parent_id[formData.parent_id.length - 1]
: formData.parent_id
}
if (formData.province) data.province = formData.province if (formData.province) data.province = formData.province
if (formData.city) data.city = formData.city if (formData.city) data.city = formData.city
if (formData.district) data.district = formData.district if (formData.district) data.district = formData.district
@@ -856,8 +843,8 @@
dialogVisible.value = false dialogVisible.value = false
await getShopList() await getShopList()
// 提交成功后也更新上级店铺列表 // 提交成功后也重新加载顶级店铺
await loadParentShopList() await loadTopLevelShops()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {

View File

@@ -830,19 +830,8 @@
} }
} }
// 检查节点是否有子节点(子菜单或按钮)
const hasChildren = (data: any): boolean => {
return data.children && data.children.length > 0
}
// 移除单个权限 // 移除单个权限
const removeSinglePermission = async (data: any) => { const removeSinglePermission = async (data: any) => {
// 检查是否有子菜单或按钮
if (hasChildren(data)) {
ElMessage.warning('该权限下还有子菜单或按钮,请先移除子项后再移除此权限')
return
}
try { try {
// 保存右侧树的展开节点 // 保存右侧树的展开节点
const expandedKeys = rightTreeRef.value?.store?.nodesMap const expandedKeys = rightTreeRef.value?.store?.nodesMap
@@ -851,10 +840,16 @@
.map((key) => Number(key)) .map((key) => Number(key))
: [] : []
await RoleService.removePermission(currentRoleId.value, data.id) // 获取要移除的所有权限ID包括当前节点和所有子节点
const idsToRemove = [data.id, ...getAllChildrenIds(data.id)]
// 更新已选权限列表 // 调用API批量移除权限传递所有需要移除的权限ID
selectedPermissions.value = selectedPermissions.value.filter((id) => id !== data.id) await RoleService.removePermissions(currentRoleId.value, idsToRemove)
// 更新已选权限列表(移除当前节点及其所有子节点)
selectedPermissions.value = selectedPermissions.value.filter(
(id) => !idsToRemove.includes(id)
)
// 重新构建左右两侧树 // 重新构建左右两侧树
const fullTreeData = buildTreeData(originalPermissionTree.value) const fullTreeData = buildTreeData(originalPermissionTree.value)