feat: 新增财务模块并优化组织管理、角色权限与我的订单
- 新增财务菜单与应收应付、充值记录、账户流水页面及路由 - 组织管理改为左右分栏,优化左侧树层级展示与选中交互 - 角色权限分配改为列表形式,支持各级菜单独立勾选 - 我的订单工具栏按钮样式与独立 loading 交互优化 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
5cd6a5462d
commit
2063621daf
1006
docs/微信登录整合方案.md
1006
docs/微信登录整合方案.md
File diff suppressed because it is too large
Load Diff
1048
docs/数据权限设计.md
Normal file
1048
docs/数据权限设计.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -508,16 +508,51 @@ const menuData = [
|
|||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
parentRoute: '/user',
|
parentRoute: '/user',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 一级菜单 - 财务
|
||||||
|
{
|
||||||
|
level: 1,
|
||||||
|
name: '财务',
|
||||||
|
permissionCode: 'Finance',
|
||||||
|
route: '/finance',
|
||||||
|
component: null,
|
||||||
|
icon: 'icon-storage',
|
||||||
|
order: 6,
|
||||||
|
hideInMenu: false,
|
||||||
|
},
|
||||||
|
// 二级菜单 - 财务
|
||||||
{
|
{
|
||||||
level: 2,
|
level: 2,
|
||||||
name: '充值记录',
|
name: '充值记录',
|
||||||
permissionCode: 'User.RechargeRecord',
|
permissionCode: 'Finance.RechargeRecord',
|
||||||
route: '/user/rechargeRecord',
|
route: '/finance/rechargeRecord',
|
||||||
component: 'views/user/recharge/rechargeRecord',
|
component: 'views/finance/rechargeRecord/index',
|
||||||
|
icon: null,
|
||||||
|
order: 1,
|
||||||
|
hideInMenu: false,
|
||||||
|
parentRoute: '/finance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 2,
|
||||||
|
name: '资金流水',
|
||||||
|
permissionCode: 'Finance.AccountFlow',
|
||||||
|
route: '/finance/accountFlow',
|
||||||
|
component: 'views/finance/accountFlow/index',
|
||||||
icon: null,
|
icon: null,
|
||||||
order: 2,
|
order: 2,
|
||||||
hideInMenu: false,
|
hideInMenu: false,
|
||||||
parentRoute: '/user',
|
parentRoute: '/finance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 2,
|
||||||
|
name: '应收应付',
|
||||||
|
permissionCode: 'Finance.ReceivablePayable',
|
||||||
|
route: '/finance/receivablePayable',
|
||||||
|
component: 'views/finance/receivablePayable/index',
|
||||||
|
icon: null,
|
||||||
|
order: 3,
|
||||||
|
hideInMenu: false,
|
||||||
|
parentRoute: '/finance',
|
||||||
},
|
},
|
||||||
|
|
||||||
// 一级菜单 - 系统
|
// 一级菜单 - 系统
|
||||||
|
|||||||
@ -103,6 +103,42 @@ server {
|
|||||||
try_files /index.html =200;
|
try_files /index.html =200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============ 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先)============
|
||||||
|
# 页面路径为 camelCase;API 为 kebab(如 /system/dict/list、/system/batch-email/…),勿用宽泛前缀匹配 dict
|
||||||
|
location = /system/dict {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location = /system/warehouseAddress {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location = /system/announcement {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/users {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/roles {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/menus {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/organizations {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/batchEmail {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/otherConfig {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/xhsAiAssistant {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
location ^~ /system/dataPerm/ {
|
||||||
|
try_files /index.html =200;
|
||||||
|
}
|
||||||
|
|
||||||
# ============ 微信公众号服务器回调(admin API,非 itfx)============
|
# ============ 微信公众号服务器回调(admin API,非 itfx)============
|
||||||
location ^~ /api/apiwechat/ {
|
location ^~ /api/apiwechat/ {
|
||||||
rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break;
|
rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break;
|
||||||
|
|||||||
@ -39,6 +39,19 @@ server {
|
|||||||
location ^~ /operation/outboundScan { try_files /index.html =200; }
|
location ^~ /operation/outboundScan { try_files /index.html =200; }
|
||||||
location ^~ /operation/printLabel { try_files /index.html =200; }
|
location ^~ /operation/printLabel { try_files /index.html =200; }
|
||||||
|
|
||||||
|
# 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先)
|
||||||
|
location = /system/dict { try_files /index.html =200; }
|
||||||
|
location = /system/warehouseAddress { try_files /index.html =200; }
|
||||||
|
location = /system/announcement { try_files /index.html =200; }
|
||||||
|
location ^~ /system/users { try_files /index.html =200; }
|
||||||
|
location ^~ /system/roles { try_files /index.html =200; }
|
||||||
|
location ^~ /system/menus { try_files /index.html =200; }
|
||||||
|
location ^~ /system/organizations { try_files /index.html =200; }
|
||||||
|
location ^~ /system/batchEmail { try_files /index.html =200; }
|
||||||
|
location ^~ /system/otherConfig { try_files /index.html =200; }
|
||||||
|
location ^~ /system/xhsAiAssistant { try_files /index.html =200; }
|
||||||
|
location ^~ /system/dataPerm/ { try_files /index.html =200; }
|
||||||
|
|
||||||
location ^~ /api/apiwechat/ {
|
location ^~ /api/apiwechat/ {
|
||||||
rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break;
|
rewrite ^/api/apiwechat/(.*)$ /admin/apiwechat/$1 break;
|
||||||
proxy_pass http://api.nanafa.com;
|
proxy_pass http://api.nanafa.com;
|
||||||
|
|||||||
57
src/api/finance/receivablePayable.ts
Normal file
57
src/api/finance/receivablePayable.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { axiosInstance } from '@/api/rbac'
|
||||||
|
import { omsUrl, parseListPayload } from '@/api/oms/common'
|
||||||
|
|
||||||
|
export interface ReceivablePayableListParams {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
keyword?: string
|
||||||
|
feeType?: string
|
||||||
|
costItem?: string
|
||||||
|
chargingStage?: string
|
||||||
|
customerId?: number
|
||||||
|
startTime?: string
|
||||||
|
endTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReceivablePayableRow {
|
||||||
|
serviceNo: string
|
||||||
|
clientNo?: string
|
||||||
|
trackNo?: string
|
||||||
|
customerCode?: string
|
||||||
|
orderStatus: string
|
||||||
|
orderStatusName?: string
|
||||||
|
isPaid: number
|
||||||
|
payTime?: string
|
||||||
|
receiveTime?: string
|
||||||
|
shipTime?: string
|
||||||
|
productCode?: string
|
||||||
|
channelCode?: string
|
||||||
|
consigneeCountryName?: string
|
||||||
|
feeType: string
|
||||||
|
feeTypeName?: string
|
||||||
|
costItem: string
|
||||||
|
amount: number
|
||||||
|
currency: string
|
||||||
|
downstreamProvider?: string
|
||||||
|
priceNo?: string
|
||||||
|
priceEffectiveTime?: string
|
||||||
|
chargingStage?: string
|
||||||
|
chargingStageName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReceivablePayableList(params: ReceivablePayableListParams) {
|
||||||
|
const res = await axiosInstance.get(omsUrl('/finance/receivable-payable/list'), {
|
||||||
|
params: {
|
||||||
|
page: params.page,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
keyword: params.keyword || undefined,
|
||||||
|
feeType: params.feeType && params.feeType !== 'all' ? params.feeType : undefined,
|
||||||
|
costItem: params.costItem || undefined,
|
||||||
|
chargingStage: params.chargingStage && params.chargingStage !== 'all' ? params.chargingStage : undefined,
|
||||||
|
customerId: params.customerId || undefined,
|
||||||
|
startTime: params.startTime || undefined,
|
||||||
|
endTime: params.endTime || undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return parseListPayload<ReceivablePayableRow>(res.data)
|
||||||
|
}
|
||||||
@ -36,6 +36,8 @@
|
|||||||
showPagination?: boolean;
|
showPagination?: boolean;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
|
/** 树形层级展示(保留缩进/展开箭头,适用于组织等多级树) */
|
||||||
|
hierarchical?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
selectedKeys: () => [],
|
selectedKeys: () => [],
|
||||||
@ -62,6 +64,7 @@
|
|||||||
showPagination: true,
|
showPagination: true,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
hierarchical: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -268,6 +271,14 @@
|
|||||||
if (nodeData) emit('nodeClick', key, nodeData);
|
if (nodeData) emit('nodeClick', key, nodeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectTreeNode(key: string) {
|
||||||
|
if (!key) return;
|
||||||
|
selectedKeysModel.value = [key];
|
||||||
|
const nodeData = getNodeData(key);
|
||||||
|
emit('select', [key], { node: nodeData });
|
||||||
|
if (nodeData) emit('nodeClick', key, nodeData);
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
getNodeData,
|
getNodeData,
|
||||||
getNodeIndex,
|
getNodeIndex,
|
||||||
@ -317,7 +328,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="interface-tree">
|
<div
|
||||||
|
class="interface-tree"
|
||||||
|
:class="{ 'interface-tree--hierarchical': hierarchical }"
|
||||||
|
>
|
||||||
<div v-if="loading" class="loading-state">
|
<div v-if="loading" class="loading-state">
|
||||||
<a-spin />
|
<a-spin />
|
||||||
<p>{{ loadingText }}</p>
|
<p>{{ loadingText }}</p>
|
||||||
@ -335,14 +349,20 @@
|
|||||||
<template #title="{ key, title }">
|
<template #title="{ key, title }">
|
||||||
<div
|
<div
|
||||||
class="tree-node"
|
class="tree-node"
|
||||||
:class="{ selected: selectedKeysModel.includes(key) }"
|
:class="{
|
||||||
|
selected: selectedKeysModel.includes(key),
|
||||||
|
'tree-node--hierarchical': hierarchical,
|
||||||
|
'tree-node--branch': hierarchical && getNodeData(key)?.isLeaf !== true,
|
||||||
|
[`tree-node--depth-${getNodeData(key)?.depth || 1}`]: hierarchical,
|
||||||
|
}"
|
||||||
|
@click="hierarchical && selectTreeNode(key)"
|
||||||
>
|
>
|
||||||
<table class="node-table">
|
<table class="node-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="node-content-cell">
|
<td class="node-content-cell">
|
||||||
<span v-if="selectedKeysModel.includes(key)" class="selected-dot"></span>
|
<span v-if="selectedKeysModel.includes(key) && !hierarchical" class="selected-dot"></span>
|
||||||
<span v-if="getNodeData(key)?.isLeaf && showNodeIndex" class="node-number">{{ getNodeIndex(key) }}</span>
|
<span v-if="getNodeData(key)?.isLeaf && showNodeIndex && !hierarchical" class="node-number">{{ getNodeIndex(key) }}</span>
|
||||||
<span v-if="getNodeData(key)?.isLeaf && showMethod" class="post-method">{{ methodLabel }}</span>
|
<span v-if="getNodeData(key)?.isLeaf && showMethod" class="post-method">{{ methodLabel }}</span>
|
||||||
<span class="node-title">{{ title }}</span>
|
<span class="node-title">{{ title }}</span>
|
||||||
<span v-if="getNodeData(key)?.isLeaf && showMapCount" class="map-count">
|
<span v-if="getNodeData(key)?.isLeaf && showMapCount" class="map-count">
|
||||||
@ -421,7 +441,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="!getNodeData(key)?.isLeaf && (getNodeData(key)?.subtitle || $slots['branch-node-subtitle'])"
|
v-if="
|
||||||
|
(hierarchical || getNodeData(key)?.isLeaf !== true) &&
|
||||||
|
(getNodeData(key)?.subtitle || $slots['branch-node-subtitle'])
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<td colspan="2" class="node-subtitle-cell">
|
<td colspan="2" class="node-subtitle-cell">
|
||||||
<slot name="branch-node-subtitle" :node-data="getNodeData(key)" :node-key="key">
|
<slot name="branch-node-subtitle" :node-data="getNodeData(key)" :node-key="key">
|
||||||
@ -616,55 +639,211 @@
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
|
|
||||||
:deep(.arco-tree),
|
&:not(.interface-tree--hierarchical) {
|
||||||
:deep(.arco-tree-show-line),
|
:deep(.arco-tree),
|
||||||
:deep(.arco-tree-size-medium),
|
:deep(.arco-tree-show-line),
|
||||||
:deep(.arco-tree-node),
|
:deep(.arco-tree-size-medium),
|
||||||
:deep(.arco-tree-node-title),
|
:deep(.arco-tree-node),
|
||||||
:deep(.arco-tree-node-title-text),
|
:deep(.arco-tree-node-title),
|
||||||
:deep(.arco-tree-node-content),
|
:deep(.arco-tree-node-title-text),
|
||||||
:deep(.arco-tree-node-content-wrapper) {
|
:deep(.arco-tree-node-content),
|
||||||
display: contents !important;
|
:deep(.arco-tree-node-content-wrapper) {
|
||||||
padding: 0 !important;
|
display: contents !important;
|
||||||
margin: 0 !important;
|
padding: 0 !important;
|
||||||
background: transparent !important;
|
margin: 0 !important;
|
||||||
border: none !important;
|
background: transparent !important;
|
||||||
box-shadow: none !important;
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-icon),
|
||||||
|
:deep(.arco-tree-node-indent),
|
||||||
|
:deep(.arco-tree-node-switcher),
|
||||||
|
:deep(.arco-icon),
|
||||||
|
:deep(.arco-tree-node-title .arco-icon),
|
||||||
|
:deep(.arco-tree-node-line),
|
||||||
|
:deep(.arco-tree-node-line-top),
|
||||||
|
:deep(.arco-tree-node-line-bottom) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-wrapper),
|
||||||
|
:deep(.arco-tree-node-selected),
|
||||||
|
:deep(.arco-tree-node-selected > .arco-tree-node-title) {
|
||||||
|
display: contents !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-content) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-list),
|
||||||
|
:deep(.arco-tree-list-holder) {
|
||||||
|
display: contents !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.arco-tree-node-icon),
|
&.interface-tree--hierarchical {
|
||||||
:deep(.arco-tree-node-indent),
|
overflow-y: auto;
|
||||||
:deep(.arco-tree-node-switcher),
|
padding-right: 2px;
|
||||||
:deep(.arco-icon),
|
|
||||||
:deep(.arco-tree-node-title .arco-icon),
|
|
||||||
:deep(.arco-tree-node-line),
|
|
||||||
:deep(.arco-tree-node-line-top),
|
|
||||||
:deep(.arco-tree-node-line-bottom) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-tree-node-wrapper),
|
:deep(.arco-tree-node) {
|
||||||
:deep(.arco-tree-node-selected),
|
align-items: flex-start;
|
||||||
:deep(.arco-tree-node-selected > .arco-tree-node-title) {
|
}
|
||||||
display: contents !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
background: transparent !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-tree-node-content) {
|
:deep(.arco-tree-node-switcher) {
|
||||||
display: block !important;
|
display: inline-flex !important;
|
||||||
}
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(255, 255, 255, 0.72);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.arco-tree-list),
|
:deep(.arco-tree-node-indent-block) {
|
||||||
:deep(.arco-tree-list-holder) {
|
display: inline-block !important;
|
||||||
display: contents !important;
|
}
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
:deep(.arco-tree-node-title),
|
||||||
|
:deep(.arco-tree-node-title-text),
|
||||||
|
:deep(.arco-tree-node-content),
|
||||||
|
:deep(.arco-tree-node-content-wrapper) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-title) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-title-text) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-selected .arco-tree-node-title),
|
||||||
|
:deep(.arco-tree-node-selected .arco-tree-node-content) {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-line),
|
||||||
|
:deep(.arco-tree-node-line-top),
|
||||||
|
:deep(.arco-tree-node-line-bottom) {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
width: 100%;
|
||||||
|
margin: 2px 0 6px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-left-width: 3px;
|
||||||
|
border-left-style: solid;
|
||||||
|
transform: none;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: #fff394 !important;
|
||||||
|
border-left-color: #fff394 !important;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #000 !important;
|
||||||
|
|
||||||
|
.node-title,
|
||||||
|
.node-subtitle {
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tree-node--branch .node-title::before {
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node--depth-1 {
|
||||||
|
border-left-color: var(--app-primary-color);
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node--depth-2 {
|
||||||
|
border-left-color: rgba(45, 140, 140, 0.75);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node--depth-3 {
|
||||||
|
border-left-color: rgba(255, 255, 255, 0.35);
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node--depth-4,
|
||||||
|
.tree-node--depth-5,
|
||||||
|
.tree-node--depth-6 {
|
||||||
|
border-left-color: rgba(255, 255, 255, 0.2);
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
padding: 6px 10px;
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node--branch .node-title::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: rgba(255, 255, 255, 0.45);
|
||||||
|
vertical-align: middle;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-subtitle {
|
||||||
|
color: rgba(255, 255, 255, 0.58);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.interface-tree--hierarchical::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interface-tree--hierarchical::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.18);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.interface-tree::-webkit-scrollbar {
|
.interface-tree::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -479,7 +479,10 @@ const menuNameToLocaleKeyMap: Record<string, string> = {
|
|||||||
'客户管理': 'menu.user.customer',
|
'客户管理': 'menu.user.customer',
|
||||||
'个人中心': 'menu.user.profile',
|
'个人中心': 'menu.user.profile',
|
||||||
'资金充值': 'menu.user.recharge',
|
'资金充值': 'menu.user.recharge',
|
||||||
'充值记录': 'menu.user.rechargeRecord',
|
'财务': 'menu.finance',
|
||||||
|
'充值记录': 'menu.finance.rechargeRecord',
|
||||||
|
'资金流水': 'menu.finance.accountFlow',
|
||||||
|
'应收应付': 'menu.finance.receivablePayable',
|
||||||
'订单': 'menu.order',
|
'订单': 'menu.order',
|
||||||
'我要查价': 'menu.order.priceCheck',
|
'我要查价': 'menu.order.priceCheck',
|
||||||
'我要下单': 'menu.order.placeOrder',
|
'我要下单': 'menu.order.placeOrder',
|
||||||
@ -539,6 +542,10 @@ const menuNameToLocaleKeyMap: Record<string, string> = {
|
|||||||
'技能卡片': 'menu.xhs.skills',
|
'技能卡片': 'menu.xhs.skills',
|
||||||
'拟人设置': 'menu.xhs.humanSettings',
|
'拟人设置': 'menu.xhs.humanSettings',
|
||||||
'AI 助手': 'menu.system.xhsAiAssistant',
|
'AI 助手': 'menu.system.xhsAiAssistant',
|
||||||
|
'数据权限': 'menu.system.dataPerm',
|
||||||
|
'用户数据权限': 'menu.system.dataPermUsers',
|
||||||
|
'权限模板': 'menu.system.dataPermTemplates',
|
||||||
|
'权限审计': 'menu.system.dataPermAudit',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 格式化菜单标题
|
// 格式化菜单标题
|
||||||
@ -904,9 +911,9 @@ const onBalanceMenuClick = (event: any) => {
|
|||||||
if (key === 'recharge') {
|
if (key === 'recharge') {
|
||||||
router.push({ name: 'VipRechargeDeposit' });
|
router.push({ name: 'VipRechargeDeposit' });
|
||||||
} else if (key === 'rechargeRecord') {
|
} else if (key === 'rechargeRecord') {
|
||||||
router.push({ name: 'VipRechargeRechargeRecord' });
|
router.push({ name: 'FinanceRechargeRecord' });
|
||||||
} else if (key === 'accountFlow') {
|
} else if (key === 'accountFlow') {
|
||||||
router.push({ name: 'UserAccountFlow' });
|
router.push({ name: 'FinanceAccountFlow' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -63,8 +63,10 @@ export default {
|
|||||||
'menu.user.subAccount': 'Sub Accounts',
|
'menu.user.subAccount': 'Sub Accounts',
|
||||||
'menu.user.profile': 'Personal Center',
|
'menu.user.profile': 'Personal Center',
|
||||||
'menu.user.recharge': 'Recharge',
|
'menu.user.recharge': 'Recharge',
|
||||||
'menu.user.rechargeRecord': 'Recharge Records',
|
'menu.finance': 'Finance',
|
||||||
'menu.user.accountFlow': 'Account Transactions',
|
'menu.finance.rechargeRecord': 'Recharge Records',
|
||||||
|
'menu.finance.accountFlow': 'Account Transactions',
|
||||||
|
'menu.finance.receivablePayable': 'Receivables & Payables',
|
||||||
'menu.arcoWebsite': 'Arco Design',
|
'menu.arcoWebsite': 'Arco Design',
|
||||||
'menu.faq': 'FAQ',
|
'menu.faq': 'FAQ',
|
||||||
'menu.example': 'Example',
|
'menu.example': 'Example',
|
||||||
@ -312,6 +314,7 @@ export default {
|
|||||||
'order.myOrders.actions.print': 'Print',
|
'order.myOrders.actions.print': 'Print',
|
||||||
'order.myOrders.actions.export': 'Export',
|
'order.myOrders.actions.export': 'Export',
|
||||||
'order.myOrders.actions.modifyOrder': 'Edit order',
|
'order.myOrders.actions.modifyOrder': 'Edit order',
|
||||||
|
'order.myOrders.actions.more': 'More',
|
||||||
|
|
||||||
'order.myOrders.pagination.totalRecords': 'Total {count} records',
|
'order.myOrders.pagination.totalRecords': 'Total {count} records',
|
||||||
'order.myOrders.pagination.perPage': 'per page',
|
'order.myOrders.pagination.perPage': 'per page',
|
||||||
@ -1020,7 +1023,7 @@ export default {
|
|||||||
// System - Role Management (Additional)
|
// System - Role Management (Additional)
|
||||||
'system.role.modal.add': 'Add Role',
|
'system.role.modal.add': 'Add Role',
|
||||||
'system.role.modal.edit': 'Edit Role',
|
'system.role.modal.edit': 'Edit Role',
|
||||||
'system.role.modal.assignPermissions': 'Assign Permissions',
|
'system.role.modal.assignPermissions': 'Assign Menus',
|
||||||
'system.role.form.name': 'Role Name',
|
'system.role.form.name': 'Role Name',
|
||||||
'system.role.form.namePlaceholder': 'Enter role name',
|
'system.role.form.namePlaceholder': 'Enter role name',
|
||||||
'system.role.form.code': 'Role Code',
|
'system.role.form.code': 'Role Code',
|
||||||
@ -1051,6 +1054,9 @@ export default {
|
|||||||
'system.role.permission.total': 'Total',
|
'system.role.permission.total': 'Total',
|
||||||
'system.role.permission.selectedCount': '{selected} / {total} selected',
|
'system.role.permission.selectedCount': '{selected} / {total} selected',
|
||||||
'system.role.permission.noMatch': 'No matching permissions',
|
'system.role.permission.noMatch': 'No matching permissions',
|
||||||
|
'system.role.permission.col.level1': 'Level 1 menu',
|
||||||
|
'system.role.permission.col.level2': 'Level 2 menu',
|
||||||
|
'system.role.permission.col.level3': 'Level 3 buttons',
|
||||||
'system.role.tag.creating': 'Creating',
|
'system.role.tag.creating': 'Creating',
|
||||||
'system.role.tag.boundWarehouse': 'Warehouse bound',
|
'system.role.tag.boundWarehouse': 'Warehouse bound',
|
||||||
'system.role.message.deleteSuccess': 'Delete successful',
|
'system.role.message.deleteSuccess': 'Delete successful',
|
||||||
@ -1124,6 +1130,12 @@ export default {
|
|||||||
'system.org.message.addSuccess': 'Add successfully',
|
'system.org.message.addSuccess': 'Add successfully',
|
||||||
'system.org.message.addFailed': 'Add failed',
|
'system.org.message.addFailed': 'Add failed',
|
||||||
'system.org.message.getDetailFailed': 'Failed to get organization details',
|
'system.org.message.getDetailFailed': 'Failed to get organization details',
|
||||||
|
'system.org.loading': 'Loading organizations...',
|
||||||
|
'system.org.level.root': 'Level 1',
|
||||||
|
'system.org.level.n': 'Level {n}',
|
||||||
|
'system.org.level.children': '{count} sub-orgs',
|
||||||
|
'system.org.users.emptySelect': 'Select an organization',
|
||||||
|
'system.org.users.emptyDesc': 'Select an organization on the left to view its users',
|
||||||
|
|
||||||
// System - Position Management
|
// System - Position Management
|
||||||
'system.position.search.placeholder.orgName': 'Enter organization name',
|
'system.position.search.placeholder.orgName': 'Enter organization name',
|
||||||
@ -1761,6 +1773,47 @@ export default {
|
|||||||
'user.accountFlow.table.balanceAfter': 'Balance after',
|
'user.accountFlow.table.balanceAfter': 'Balance after',
|
||||||
'user.accountFlow.table.remark': 'Remark',
|
'user.accountFlow.table.remark': 'Remark',
|
||||||
'user.accountFlow.table.customerCode': 'Customer code',
|
'user.accountFlow.table.customerCode': 'Customer code',
|
||||||
|
|
||||||
|
// Finance - receivable & payable
|
||||||
|
'finance.receivablePayable.search.keywordPlaceholder': 'Service no. / client no. / track no.',
|
||||||
|
'finance.receivablePayable.search.feeTypePlaceholder': 'Fee type',
|
||||||
|
'finance.receivablePayable.search.feeTypeAll': 'All',
|
||||||
|
'finance.receivablePayable.search.costItemPlaceholder': 'Cost item',
|
||||||
|
'finance.receivablePayable.search.chargingStagePlaceholder': 'Charging stage',
|
||||||
|
'finance.receivablePayable.search.chargingStageAll': 'All',
|
||||||
|
'finance.receivablePayable.search.customerIdPlaceholder': 'Customer ID (optional)',
|
||||||
|
'finance.receivablePayable.search.dateRange': 'Order created time',
|
||||||
|
'finance.receivablePayable.search.actions.search': 'Search',
|
||||||
|
'finance.receivablePayable.search.actions.reset': 'Reset',
|
||||||
|
'finance.receivablePayable.feeType.receivable': 'Receivable',
|
||||||
|
'finance.receivablePayable.feeType.payable': 'Payable',
|
||||||
|
'finance.receivablePayable.chargingStage.forecast': 'Forecast',
|
||||||
|
'finance.receivablePayable.chargingStage.inbound': 'Inbound',
|
||||||
|
'finance.receivablePayable.chargingStage.outbound': 'Outbound',
|
||||||
|
'finance.receivablePayable.isPaid.yes': 'Paid',
|
||||||
|
'finance.receivablePayable.isPaid.no': 'Unpaid',
|
||||||
|
'finance.receivablePayable.message.loadError': 'Failed to load receivables & payables',
|
||||||
|
'finance.receivablePayable.table.serviceNo': 'Service no.',
|
||||||
|
'finance.receivablePayable.table.clientNo': 'Client no.',
|
||||||
|
'finance.receivablePayable.table.trackNo': 'Track no.',
|
||||||
|
'finance.receivablePayable.table.customerCode': 'Customer code',
|
||||||
|
'finance.receivablePayable.table.feeType': 'Fee type',
|
||||||
|
'finance.receivablePayable.table.costItem': 'Cost item',
|
||||||
|
'finance.receivablePayable.table.amount': 'Amount',
|
||||||
|
'finance.receivablePayable.table.currency': 'Currency',
|
||||||
|
'finance.receivablePayable.table.chargingStage': 'Charging stage',
|
||||||
|
'finance.receivablePayable.table.orderStatus': 'Order status',
|
||||||
|
'finance.receivablePayable.table.isPaid': 'Payment status',
|
||||||
|
'finance.receivablePayable.table.productCode': 'Product code',
|
||||||
|
'finance.receivablePayable.table.channelCode': 'Channel code',
|
||||||
|
'finance.receivablePayable.table.consigneeCountryName': 'Destination country',
|
||||||
|
'finance.receivablePayable.table.downstreamProvider': 'Downstream provider',
|
||||||
|
'finance.receivablePayable.table.priceNo': 'Price no.',
|
||||||
|
'finance.receivablePayable.table.priceEffectiveTime': 'Price effective time',
|
||||||
|
'finance.receivablePayable.table.payTime': 'Pay time',
|
||||||
|
'finance.receivablePayable.table.receiveTime': 'Receive time',
|
||||||
|
'finance.receivablePayable.table.shipTime': 'Ship time',
|
||||||
|
|
||||||
'user.recharge.record.message.exportError': 'Export failed',
|
'user.recharge.record.message.exportError': 'Export failed',
|
||||||
'user.recharge.record.pagination.total': '{count} total',
|
'user.recharge.record.pagination.total': '{count} total',
|
||||||
'user.recharge.record.pagination.prev': 'Prev',
|
'user.recharge.record.pagination.prev': 'Prev',
|
||||||
|
|||||||
@ -63,8 +63,10 @@ export default {
|
|||||||
'menu.user.subAccount': '子账号',
|
'menu.user.subAccount': '子账号',
|
||||||
'menu.user.profile': '个人中心',
|
'menu.user.profile': '个人中心',
|
||||||
'menu.user.recharge': '资金充值',
|
'menu.user.recharge': '资金充值',
|
||||||
'menu.user.rechargeRecord': '充值记录',
|
'menu.finance': '财务',
|
||||||
'menu.user.accountFlow': '资金流水',
|
'menu.finance.rechargeRecord': '充值记录',
|
||||||
|
'menu.finance.accountFlow': '资金流水',
|
||||||
|
'menu.finance.receivablePayable': '应收应付',
|
||||||
'menu.arcoWebsite': 'Arco Design',
|
'menu.arcoWebsite': 'Arco Design',
|
||||||
'menu.faq': '常见问题',
|
'menu.faq': '常见问题',
|
||||||
'menu.example': '示例页',
|
'menu.example': '示例页',
|
||||||
@ -198,6 +200,7 @@ export default {
|
|||||||
'order.myOrders.actions.print': '打印',
|
'order.myOrders.actions.print': '打印',
|
||||||
'order.myOrders.actions.export': '导出',
|
'order.myOrders.actions.export': '导出',
|
||||||
'order.myOrders.actions.modifyOrder': '修改订单',
|
'order.myOrders.actions.modifyOrder': '修改订单',
|
||||||
|
'order.myOrders.actions.more': '更多',
|
||||||
|
|
||||||
'order.myOrders.pagination.totalRecords': '共{count}条记录',
|
'order.myOrders.pagination.totalRecords': '共{count}条记录',
|
||||||
'order.myOrders.pagination.perPage': '条/页',
|
'order.myOrders.pagination.perPage': '条/页',
|
||||||
@ -906,7 +909,7 @@ export default {
|
|||||||
// 系统 - 角色管理(补充)
|
// 系统 - 角色管理(补充)
|
||||||
'system.role.modal.add': '新增角色',
|
'system.role.modal.add': '新增角色',
|
||||||
'system.role.modal.edit': '编辑角色',
|
'system.role.modal.edit': '编辑角色',
|
||||||
'system.role.modal.assignPermissions': '分配权限',
|
'system.role.modal.assignPermissions': '分配菜单',
|
||||||
'system.role.form.name': '角色名称',
|
'system.role.form.name': '角色名称',
|
||||||
'system.role.form.namePlaceholder': '请输入角色名称',
|
'system.role.form.namePlaceholder': '请输入角色名称',
|
||||||
'system.role.form.code': '角色编码',
|
'system.role.form.code': '角色编码',
|
||||||
@ -937,6 +940,9 @@ export default {
|
|||||||
'system.role.permission.total': '总计',
|
'system.role.permission.total': '总计',
|
||||||
'system.role.permission.selectedCount': '已选 {selected} / 共 {total}',
|
'system.role.permission.selectedCount': '已选 {selected} / 共 {total}',
|
||||||
'system.role.permission.noMatch': '未找到匹配的权限',
|
'system.role.permission.noMatch': '未找到匹配的权限',
|
||||||
|
'system.role.permission.col.level1': '一级菜单',
|
||||||
|
'system.role.permission.col.level2': '二级菜单',
|
||||||
|
'system.role.permission.col.level3': '三级按钮',
|
||||||
'system.role.tag.creating': '新建中',
|
'system.role.tag.creating': '新建中',
|
||||||
'system.role.tag.boundWarehouse': '已绑仓库',
|
'system.role.tag.boundWarehouse': '已绑仓库',
|
||||||
'system.role.message.deleteSuccess': '删除成功',
|
'system.role.message.deleteSuccess': '删除成功',
|
||||||
@ -1010,6 +1016,12 @@ export default {
|
|||||||
'system.org.message.addSuccess': '新增成功',
|
'system.org.message.addSuccess': '新增成功',
|
||||||
'system.org.message.addFailed': '新增失败',
|
'system.org.message.addFailed': '新增失败',
|
||||||
'system.org.message.getDetailFailed': '获取组织详情失败',
|
'system.org.message.getDetailFailed': '获取组织详情失败',
|
||||||
|
'system.org.loading': '正在加载组织...',
|
||||||
|
'system.org.level.root': '一级组织',
|
||||||
|
'system.org.level.n': '{n} 级组织',
|
||||||
|
'system.org.level.children': '{count} 个下级',
|
||||||
|
'system.org.users.emptySelect': '请选择组织',
|
||||||
|
'system.org.users.emptyDesc': '请从左侧选择组织,查看该组织下的用户列表',
|
||||||
|
|
||||||
// 系统 - 职位管理
|
// 系统 - 职位管理
|
||||||
'system.position.search.placeholder.orgName': '请输入组织名称',
|
'system.position.search.placeholder.orgName': '请输入组织名称',
|
||||||
@ -1780,6 +1792,47 @@ export default {
|
|||||||
'user.accountFlow.table.balanceAfter': '变动后余额',
|
'user.accountFlow.table.balanceAfter': '变动后余额',
|
||||||
'user.accountFlow.table.remark': '备注',
|
'user.accountFlow.table.remark': '备注',
|
||||||
'user.accountFlow.table.customerCode': '客户编码',
|
'user.accountFlow.table.customerCode': '客户编码',
|
||||||
|
|
||||||
|
// 财务 - 应收应付
|
||||||
|
'finance.receivablePayable.search.keywordPlaceholder': '运单号 / 客户单号 / 跟踪号',
|
||||||
|
'finance.receivablePayable.search.feeTypePlaceholder': '费用类型',
|
||||||
|
'finance.receivablePayable.search.feeTypeAll': '全部',
|
||||||
|
'finance.receivablePayable.search.costItemPlaceholder': '费用项',
|
||||||
|
'finance.receivablePayable.search.chargingStagePlaceholder': '计费阶段',
|
||||||
|
'finance.receivablePayable.search.chargingStageAll': '全部',
|
||||||
|
'finance.receivablePayable.search.customerIdPlaceholder': '客户ID(选填)',
|
||||||
|
'finance.receivablePayable.search.dateRange': '订单创建时间',
|
||||||
|
'finance.receivablePayable.search.actions.search': '搜索',
|
||||||
|
'finance.receivablePayable.search.actions.reset': '重置',
|
||||||
|
'finance.receivablePayable.feeType.receivable': '应收',
|
||||||
|
'finance.receivablePayable.feeType.payable': '应付',
|
||||||
|
'finance.receivablePayable.chargingStage.forecast': '预报',
|
||||||
|
'finance.receivablePayable.chargingStage.inbound': '入库',
|
||||||
|
'finance.receivablePayable.chargingStage.outbound': '出库',
|
||||||
|
'finance.receivablePayable.isPaid.yes': '已支付',
|
||||||
|
'finance.receivablePayable.isPaid.no': '未支付',
|
||||||
|
'finance.receivablePayable.message.loadError': '加载应收应付失败',
|
||||||
|
'finance.receivablePayable.table.serviceNo': '运单号',
|
||||||
|
'finance.receivablePayable.table.clientNo': '客户单号',
|
||||||
|
'finance.receivablePayable.table.trackNo': '跟踪号',
|
||||||
|
'finance.receivablePayable.table.customerCode': '客户编码',
|
||||||
|
'finance.receivablePayable.table.feeType': '费用类型',
|
||||||
|
'finance.receivablePayable.table.costItem': '费用项',
|
||||||
|
'finance.receivablePayable.table.amount': '金额',
|
||||||
|
'finance.receivablePayable.table.currency': '币种',
|
||||||
|
'finance.receivablePayable.table.chargingStage': '计费阶段',
|
||||||
|
'finance.receivablePayable.table.orderStatus': '订单状态',
|
||||||
|
'finance.receivablePayable.table.isPaid': '支付状态',
|
||||||
|
'finance.receivablePayable.table.productCode': '产品编码',
|
||||||
|
'finance.receivablePayable.table.channelCode': '渠道编码',
|
||||||
|
'finance.receivablePayable.table.consigneeCountryName': '目的国',
|
||||||
|
'finance.receivablePayable.table.downstreamProvider': '下游服务商',
|
||||||
|
'finance.receivablePayable.table.priceNo': '价格编号',
|
||||||
|
'finance.receivablePayable.table.priceEffectiveTime': '价格生效时间',
|
||||||
|
'finance.receivablePayable.table.payTime': '支付时间',
|
||||||
|
'finance.receivablePayable.table.receiveTime': '收货时间',
|
||||||
|
'finance.receivablePayable.table.shipTime': '发货时间',
|
||||||
|
|
||||||
'user.recharge.record.message.exportError': '导出失败',
|
'user.recharge.record.message.exportError': '导出失败',
|
||||||
'user.recharge.record.pagination.total': '共 {count} 条',
|
'user.recharge.record.pagination.total': '共 {count} 条',
|
||||||
'user.recharge.record.pagination.prev': '上一页',
|
'user.recharge.record.pagination.prev': '上一页',
|
||||||
|
|||||||
48
src/router/routes/newModules/finance.ts
Normal file
48
src/router/routes/newModules/finance.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { DEFAULT_LAYOUT } from '../base';
|
||||||
|
import { AppRouteRecordRaw } from '../types';
|
||||||
|
|
||||||
|
const FINANCE: AppRouteRecordRaw = {
|
||||||
|
path: '/finance',
|
||||||
|
name: 'Finance',
|
||||||
|
component: DEFAULT_LAYOUT,
|
||||||
|
meta: {
|
||||||
|
locale: 'menu.finance',
|
||||||
|
icon: 'icon-storage',
|
||||||
|
requiresAuth: true,
|
||||||
|
order: 6,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'rechargeRecord',
|
||||||
|
name: 'FinanceRechargeRecord',
|
||||||
|
component: () => import('@/views/finance/rechargeRecord/index.vue'),
|
||||||
|
meta: {
|
||||||
|
locale: 'menu.finance.rechargeRecord',
|
||||||
|
requiresAuth: true,
|
||||||
|
roles: ['*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'accountFlow',
|
||||||
|
name: 'FinanceAccountFlow',
|
||||||
|
component: () => import('@/views/finance/accountFlow/index.vue'),
|
||||||
|
meta: {
|
||||||
|
locale: 'menu.finance.accountFlow',
|
||||||
|
requiresAuth: true,
|
||||||
|
roles: ['*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'receivablePayable',
|
||||||
|
name: 'FinanceReceivablePayable',
|
||||||
|
component: () => import('@/views/finance/receivablePayable/index.vue'),
|
||||||
|
meta: {
|
||||||
|
locale: 'menu.finance.receivablePayable',
|
||||||
|
requiresAuth: true,
|
||||||
|
roles: ['*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FINANCE;
|
||||||
@ -55,26 +55,6 @@ const USER: AppRouteRecordRaw = {
|
|||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'rechargeRecord',
|
|
||||||
name: 'VipRechargeRechargeRecord',
|
|
||||||
component: () => import('@/views/user/recharge/rechargeRecord.vue'),
|
|
||||||
meta: {
|
|
||||||
locale: 'menu.user.rechargeRecord',
|
|
||||||
requiresAuth: true,
|
|
||||||
roles: ['*'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'accountFlow',
|
|
||||||
name: 'UserAccountFlow',
|
|
||||||
component: () => import('@/views/user/accountFlow/index.vue'),
|
|
||||||
meta: {
|
|
||||||
locale: 'menu.user.accountFlow',
|
|
||||||
requiresAuth: true,
|
|
||||||
roles: ['*'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -61,7 +61,9 @@ export function resolveLoginRouteNameFromPath(path: string): string {
|
|||||||
path.startsWith('/user/profile') ||
|
path.startsWith('/user/profile') ||
|
||||||
path.startsWith('/user/recharge') ||
|
path.startsWith('/user/recharge') ||
|
||||||
path.startsWith('/user/sub-account') ||
|
path.startsWith('/user/sub-account') ||
|
||||||
path === '/user/rechargeRecord'
|
path.startsWith('/finance/rechargeRecord') ||
|
||||||
|
path.startsWith('/finance/accountFlow') ||
|
||||||
|
path.startsWith('/finance/receivablePayable')
|
||||||
) {
|
) {
|
||||||
return CUSTOMER_LOGIN_ROUTE_NAME
|
return CUSTOMER_LOGIN_ROUTE_NAME
|
||||||
}
|
}
|
||||||
|
|||||||
98
src/utils/orgLeftMenuTree.ts
Normal file
98
src/utils/orgLeftMenuTree.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import type { OrganizationTreeDto } from '@/api/rbac'
|
||||||
|
import type { CustomLeftMenuTreeNode } from '@/components/CustomLeftMenu/types'
|
||||||
|
|
||||||
|
export function buildOrganizationLeftMenuTree(
|
||||||
|
data: OrganizationTreeDto[],
|
||||||
|
getTypeLabel?: (type?: number, depth?: number, childCount?: number) => string,
|
||||||
|
depth = 1
|
||||||
|
): CustomLeftMenuTreeNode[] {
|
||||||
|
return data.map((item) => {
|
||||||
|
const hasChildren = Boolean(item.children?.length)
|
||||||
|
const childCount = item.children?.length ?? 0
|
||||||
|
return {
|
||||||
|
key: `org-${item.id}`,
|
||||||
|
title: item.name || item.code || '',
|
||||||
|
isLeaf: !hasChildren,
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
code: item.code || '',
|
||||||
|
depth,
|
||||||
|
childCount,
|
||||||
|
subtitle: getTypeLabel?.(item.type, depth, childCount),
|
||||||
|
children: hasChildren
|
||||||
|
? buildOrganizationLeftMenuTree(item.children!, getTypeLabel, depth + 1)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findOrganizationByMenuKey(
|
||||||
|
key: string,
|
||||||
|
tree: OrganizationTreeDto[]
|
||||||
|
): OrganizationTreeDto | undefined {
|
||||||
|
if (!key.startsWith('org-')) return undefined
|
||||||
|
const id = key.slice(4)
|
||||||
|
|
||||||
|
function walk(nodes: OrganizationTreeDto[]): OrganizationTreeDto | undefined {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.id === id) return node
|
||||||
|
if (node.children?.length) {
|
||||||
|
const found = walk(node.children)
|
||||||
|
if (found) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return walk(tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterOrganizationMenuTree(
|
||||||
|
nodes: CustomLeftMenuTreeNode[],
|
||||||
|
keyword: string
|
||||||
|
): CustomLeftMenuTreeNode[] {
|
||||||
|
const trimmed = keyword.trim()
|
||||||
|
if (!trimmed) return nodes
|
||||||
|
|
||||||
|
const lower = trimmed.toLowerCase()
|
||||||
|
|
||||||
|
function filter(list: CustomLeftMenuTreeNode[]): CustomLeftMenuTreeNode[] {
|
||||||
|
const result: CustomLeftMenuTreeNode[] = []
|
||||||
|
list.forEach((node) => {
|
||||||
|
const children = node.children?.length ? filter(node.children) : []
|
||||||
|
const selfMatch =
|
||||||
|
node.title?.toLowerCase().includes(lower) ||
|
||||||
|
node.code?.toLowerCase().includes(lower)
|
||||||
|
if (selfMatch || children.length) {
|
||||||
|
result.push({
|
||||||
|
...node,
|
||||||
|
children: children.length ? children : selfMatch ? node.children : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectOrganizationMenuExpandKeys(
|
||||||
|
nodes: CustomLeftMenuTreeNode[],
|
||||||
|
maxDepth = 2,
|
||||||
|
depth = 1
|
||||||
|
): string[] {
|
||||||
|
const keys: string[] = []
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
if (node.children?.length && depth <= maxDepth) {
|
||||||
|
keys.push(node.key)
|
||||||
|
keys.push(...collectOrganizationMenuExpandKeys(node.children, maxDepth, depth + 1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findFirstOrganizationMenuKey(nodes: CustomLeftMenuTreeNode[]): string | undefined {
|
||||||
|
if (!nodes.length) return undefined
|
||||||
|
const first = nodes[0]
|
||||||
|
return first.key
|
||||||
|
}
|
||||||
113
src/utils/rbacPermissionAssignRows.ts
Normal file
113
src/utils/rbacPermissionAssignRows.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import type { PermissionDefinitionDto, PermissionGroupDto } from '@/api/rbac'
|
||||||
|
|
||||||
|
export interface PermissionAssignButton {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PermissionAssignRow {
|
||||||
|
rowKey: string
|
||||||
|
level1Code: string
|
||||||
|
level1Name: string
|
||||||
|
level2Code: string
|
||||||
|
level2Name: string
|
||||||
|
/** 二级菜单下是否还有按钮级子权限 */
|
||||||
|
level2HasChildren: boolean
|
||||||
|
buttons: PermissionAssignButton[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPermissionAssignRows(
|
||||||
|
groups: PermissionGroupDto[]
|
||||||
|
): PermissionAssignRow[] {
|
||||||
|
const rows: PermissionAssignRow[] = []
|
||||||
|
|
||||||
|
groups.forEach((group) => {
|
||||||
|
const level1Code = group.code || ''
|
||||||
|
const level1Name = group.name || level1Code
|
||||||
|
;(group.permissions || []).forEach((perm) => {
|
||||||
|
const row = mapPermissionToRow(perm, level1Code, level1Name)
|
||||||
|
if (row) rows.push(row)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapPermissionToRow(
|
||||||
|
perm: PermissionDefinitionDto,
|
||||||
|
level1Code: string,
|
||||||
|
level1Name: string
|
||||||
|
): PermissionAssignRow | null {
|
||||||
|
const level2Code = perm.code || ''
|
||||||
|
const level2Name = perm.name || level2Code
|
||||||
|
if (!level2Code) return null
|
||||||
|
|
||||||
|
const childButtons = (perm.children || [])
|
||||||
|
.filter((child) => child.code)
|
||||||
|
.map((child) => ({
|
||||||
|
code: child.code!,
|
||||||
|
name: child.name || child.code!,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
rowKey: level2Code,
|
||||||
|
level1Code,
|
||||||
|
level1Name,
|
||||||
|
level2Code,
|
||||||
|
level2Name,
|
||||||
|
level2HasChildren: childButtons.length > 0,
|
||||||
|
buttons: childButtons,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeLevel1RowSpans(rows: PermissionAssignRow[]): number[] {
|
||||||
|
const spans = new Array<number>(rows.length).fill(1)
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
while (index < rows.length) {
|
||||||
|
let end = index + 1
|
||||||
|
while (end < rows.length && rows[end].level1Code === rows[index].level1Code) {
|
||||||
|
end += 1
|
||||||
|
}
|
||||||
|
spans[index] = end - index
|
||||||
|
for (let i = index + 1; i < end; i += 1) {
|
||||||
|
spans[i] = 0
|
||||||
|
}
|
||||||
|
index = end
|
||||||
|
}
|
||||||
|
|
||||||
|
return spans
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterPermissionAssignRows(
|
||||||
|
rows: PermissionAssignRow[],
|
||||||
|
keyword: string
|
||||||
|
): PermissionAssignRow[] {
|
||||||
|
const trimmed = keyword.trim()
|
||||||
|
if (!trimmed) return rows
|
||||||
|
|
||||||
|
const lower = trimmed.toLowerCase()
|
||||||
|
return rows.filter((row) => {
|
||||||
|
if (row.level1Name.toLowerCase().includes(lower)) return true
|
||||||
|
if (row.level1Code.toLowerCase().includes(lower)) return true
|
||||||
|
if (row.level2Name.toLowerCase().includes(lower)) return true
|
||||||
|
if (row.level2Code.toLowerCase().includes(lower)) return true
|
||||||
|
return row.buttons.some(
|
||||||
|
(btn) =>
|
||||||
|
btn.name.toLowerCase().includes(lower) ||
|
||||||
|
btn.code.toLowerCase().includes(lower)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectPermissionCodesFromRows(rows: PermissionAssignRow[]): string[] {
|
||||||
|
const codes = new Set<string>()
|
||||||
|
rows.forEach((row) => {
|
||||||
|
if (row.level1Code) codes.add(row.level1Code)
|
||||||
|
if (row.level2Code) codes.add(row.level2Code)
|
||||||
|
row.buttons.forEach((btn) => {
|
||||||
|
if (btn.code) codes.add(btn.code)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return Array.from(codes)
|
||||||
|
}
|
||||||
@ -92,8 +92,14 @@ const routeNameMap: Record<string, string> = {
|
|||||||
'/user': 'User',
|
'/user': 'User',
|
||||||
'user_recharge': 'VipRechargeDeposit',
|
'user_recharge': 'VipRechargeDeposit',
|
||||||
'/user/recharge': 'VipRechargeDeposit',
|
'/user/recharge': 'VipRechargeDeposit',
|
||||||
'user_recharge_record': 'VipRechargeRechargeRecord',
|
'finance': 'Finance',
|
||||||
'/user/rechargeRecord': 'VipRechargeRechargeRecord',
|
'/finance': 'Finance',
|
||||||
|
'finance_recharge_record': 'FinanceRechargeRecord',
|
||||||
|
'/finance/rechargeRecord': 'FinanceRechargeRecord',
|
||||||
|
'finance_account_flow': 'FinanceAccountFlow',
|
||||||
|
'/finance/accountFlow': 'FinanceAccountFlow',
|
||||||
|
'finance_receivable_payable': 'FinanceReceivablePayable',
|
||||||
|
'/finance/receivablePayable': 'FinanceReceivablePayable',
|
||||||
|
|
||||||
// 渠道相关
|
// 渠道相关
|
||||||
'channel': 'Channel',
|
'channel': 'Channel',
|
||||||
|
|||||||
@ -91,7 +91,7 @@ import dayjs from 'dayjs'
|
|||||||
import { getAccountFlowList } from '@/api/customer/accountFlow'
|
import { getAccountFlowList } from '@/api/customer/accountFlow'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'UserAccountFlow',
|
name: 'FinanceAccountFlow',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
299
src/views/finance/receivablePayable/index.vue
Normal file
299
src/views/finance/receivablePayable/index.vue
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
<template>
|
||||||
|
<div class="receivable-payable-page">
|
||||||
|
<div class="receivable-payable-page-card">
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<a-space wrap :size="12">
|
||||||
|
<a-input
|
||||||
|
v-model="keyword"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('finance.receivablePayable.search.keywordPlaceholder')"
|
||||||
|
style="width: 220px"
|
||||||
|
@press-enter="handleSearch"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-model="feeTypeFilter"
|
||||||
|
:placeholder="t('finance.receivablePayable.search.feeTypePlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
style="width: 140px"
|
||||||
|
>
|
||||||
|
<a-option value="all">{{ t('finance.receivablePayable.search.feeTypeAll') }}</a-option>
|
||||||
|
<a-option value="receivable">{{ t('finance.receivablePayable.feeType.receivable') }}</a-option>
|
||||||
|
<a-option value="payable">{{ t('finance.receivablePayable.feeType.payable') }}</a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-input
|
||||||
|
v-model="costItem"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('finance.receivablePayable.search.costItemPlaceholder')"
|
||||||
|
style="width: 140px"
|
||||||
|
@press-enter="handleSearch"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-model="chargingStageFilter"
|
||||||
|
:placeholder="t('finance.receivablePayable.search.chargingStagePlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
style="width: 140px"
|
||||||
|
>
|
||||||
|
<a-option value="all">{{ t('finance.receivablePayable.search.chargingStageAll') }}</a-option>
|
||||||
|
<a-option value="forecast">{{ t('finance.receivablePayable.chargingStage.forecast') }}</a-option>
|
||||||
|
<a-option value="inbound">{{ t('finance.receivablePayable.chargingStage.inbound') }}</a-option>
|
||||||
|
<a-option value="outbound">{{ t('finance.receivablePayable.chargingStage.outbound') }}</a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-input-number
|
||||||
|
v-model="customerIdFilter"
|
||||||
|
:placeholder="t('finance.receivablePayable.search.customerIdPlaceholder')"
|
||||||
|
:min="1"
|
||||||
|
:precision="0"
|
||||||
|
allow-clear
|
||||||
|
hide-button
|
||||||
|
style="width: 140px"
|
||||||
|
/>
|
||||||
|
<a-range-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
show-time
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:placeholder="[
|
||||||
|
t('finance.receivablePayable.search.dateRange'),
|
||||||
|
t('finance.receivablePayable.search.dateRange'),
|
||||||
|
]"
|
||||||
|
style="width: 380px"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" @click="handleSearch">
|
||||||
|
{{ t('finance.receivablePayable.search.actions.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleReset">
|
||||||
|
{{ t('finance.receivablePayable.search.actions.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<div class="pagination-controls">
|
||||||
|
<PaginationPro
|
||||||
|
:total-count="totalCount"
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin class="list-spin" :loading="listLoading">
|
||||||
|
<div ref="tableContainerRef" class="list-wrap">
|
||||||
|
<HandsontablePro
|
||||||
|
:columns="hotColumns"
|
||||||
|
:data="tableRows"
|
||||||
|
:total="totalCount"
|
||||||
|
:current-page="currentPage"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:read-only="true"
|
||||||
|
:stretch-columns="true"
|
||||||
|
:custom-cell-renderer="customCellRenderer"
|
||||||
|
:height="handsontableHeight"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||||||
|
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||||||
|
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import {
|
||||||
|
getReceivablePayableList,
|
||||||
|
type ReceivablePayableRow,
|
||||||
|
} from '@/api/finance/receivablePayable'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'FinanceReceivablePayable',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||||
|
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 400))
|
||||||
|
|
||||||
|
const tableRows = ref<ReceivablePayableRow[]>([])
|
||||||
|
const totalCount = ref(0)
|
||||||
|
const listLoading = ref(false)
|
||||||
|
|
||||||
|
const keyword = ref('')
|
||||||
|
const feeTypeFilter = ref('all')
|
||||||
|
const costItem = ref('')
|
||||||
|
const chargingStageFilter = ref('all')
|
||||||
|
const customerIdFilter = ref<number | undefined>(undefined)
|
||||||
|
const dateRange = ref<any[]>([])
|
||||||
|
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(50)
|
||||||
|
|
||||||
|
const hotColumns = computed(() => [
|
||||||
|
{ key: 'serviceNo', label: t('finance.receivablePayable.table.serviceNo'), width: 160 },
|
||||||
|
{ key: 'clientNo', label: t('finance.receivablePayable.table.clientNo'), width: 140 },
|
||||||
|
{ key: 'trackNo', label: t('finance.receivablePayable.table.trackNo'), width: 140 },
|
||||||
|
{ key: 'customerCode', label: t('finance.receivablePayable.table.customerCode'), width: 110 },
|
||||||
|
{ key: 'feeTypeName', label: t('finance.receivablePayable.table.feeType'), width: 90 },
|
||||||
|
{ key: 'costItem', label: t('finance.receivablePayable.table.costItem'), width: 120 },
|
||||||
|
{ key: 'amount', label: t('finance.receivablePayable.table.amount'), width: 100 },
|
||||||
|
{ key: 'currency', label: t('finance.receivablePayable.table.currency'), width: 80 },
|
||||||
|
{ key: 'chargingStageName', label: t('finance.receivablePayable.table.chargingStage'), width: 90 },
|
||||||
|
{ key: 'orderStatusName', label: t('finance.receivablePayable.table.orderStatus'), width: 100 },
|
||||||
|
{ key: 'isPaid', label: t('finance.receivablePayable.table.isPaid'), width: 90 },
|
||||||
|
{ key: 'productCode', label: t('finance.receivablePayable.table.productCode'), width: 120 },
|
||||||
|
{ key: 'channelCode', label: t('finance.receivablePayable.table.channelCode'), width: 120 },
|
||||||
|
{ key: 'consigneeCountryName', label: t('finance.receivablePayable.table.consigneeCountryName'), width: 100 },
|
||||||
|
{ key: 'downstreamProvider', label: t('finance.receivablePayable.table.downstreamProvider'), width: 120 },
|
||||||
|
{ key: 'priceNo', label: t('finance.receivablePayable.table.priceNo'), width: 120 },
|
||||||
|
{ key: 'priceEffectiveTime', label: t('finance.receivablePayable.table.priceEffectiveTime'), width: 170 },
|
||||||
|
{ key: 'payTime', label: t('finance.receivablePayable.table.payTime'), width: 170 },
|
||||||
|
{ key: 'receiveTime', label: t('finance.receivablePayable.table.receiveTime'), width: 170 },
|
||||||
|
{ key: 'shipTime', label: t('finance.receivablePayable.table.shipTime'), width: 170 },
|
||||||
|
])
|
||||||
|
|
||||||
|
function formatMoney(value: unknown) {
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isFinite(num) ? num.toFixed(2) : '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
const customCellRenderer = (rowData: ReceivablePayableRow, colKey: string, value: any) => {
|
||||||
|
if (colKey === 'amount') {
|
||||||
|
const num = Number(value ?? 0)
|
||||||
|
const color = rowData.feeType === 'payable' ? '#f53f3f' : '#00b42a'
|
||||||
|
return `<span style="color:${color};font-weight:500;">${formatMoney(num)}</span>`
|
||||||
|
}
|
||||||
|
if (colKey === 'isPaid') {
|
||||||
|
const paid = Number(value) === 1
|
||||||
|
const text = paid
|
||||||
|
? t('finance.receivablePayable.isPaid.yes')
|
||||||
|
: t('finance.receivablePayable.isPaid.no')
|
||||||
|
const style = paid
|
||||||
|
? 'color:#00b42a;background:#e8ffea;border:1px solid #b9f0c2;'
|
||||||
|
: 'color:#4e5969;background:#f2f3f5;border:1px solid #e5e6eb;'
|
||||||
|
return `<span style="display:inline-flex;align-items:center;height:24px;padding:0 10px;border-radius:4px;font-size:12px;font-weight:500;${style}">${text}</span>`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildListParams() {
|
||||||
|
const params: Record<string, any> = {
|
||||||
|
page: currentPage.value,
|
||||||
|
pageSize: pageSize.value,
|
||||||
|
}
|
||||||
|
const kw = keyword.value.trim()
|
||||||
|
if (kw) params.keyword = kw
|
||||||
|
if (feeTypeFilter.value && feeTypeFilter.value !== 'all') {
|
||||||
|
params.feeType = feeTypeFilter.value
|
||||||
|
}
|
||||||
|
const costItemValue = costItem.value.trim()
|
||||||
|
if (costItemValue) params.costItem = costItemValue
|
||||||
|
if (chargingStageFilter.value && chargingStageFilter.value !== 'all') {
|
||||||
|
params.chargingStage = chargingStageFilter.value
|
||||||
|
}
|
||||||
|
if (customerIdFilter.value != null && !Number.isNaN(customerIdFilter.value)) {
|
||||||
|
params.customerId = customerIdFilter.value
|
||||||
|
}
|
||||||
|
if (dateRange.value?.length === 2 && dateRange.value[0] && dateRange.value[1]) {
|
||||||
|
params.startTime = dayjs(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
params.endTime = dayjs(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadList() {
|
||||||
|
listLoading.value = true
|
||||||
|
try {
|
||||||
|
const { items, total } = await getReceivablePayableList(buildListParams() as any)
|
||||||
|
totalCount.value = total
|
||||||
|
tableRows.value = items || []
|
||||||
|
} catch (e: any) {
|
||||||
|
Message.error(e?.message || t('finance.receivablePayable.message.loadError'))
|
||||||
|
tableRows.value = []
|
||||||
|
totalCount.value = 0
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
currentPage.value = 1
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
keyword.value = ''
|
||||||
|
feeTypeFilter.value = 'all'
|
||||||
|
costItem.value = ''
|
||||||
|
chargingStageFilter.value = 'all'
|
||||||
|
customerIdFilter.value = undefined
|
||||||
|
dateRange.value = []
|
||||||
|
currentPage.value = 1
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
currentPage.value = page
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(size: number) {
|
||||||
|
pageSize.value = size
|
||||||
|
currentPage.value = 1
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.receivable-payable-page {
|
||||||
|
padding: 16px;
|
||||||
|
background: #f0f3f4;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-payable-page-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-spin {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -142,7 +142,7 @@ import {
|
|||||||
} from '@/api/recharge/rechargeRecord'
|
} from '@/api/recharge/rechargeRecord'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VipRechargeRechargeRecord',
|
name: 'FinanceRechargeRecord',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted, nextTick } from 'vue';
|
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||||
import { IconSettings } from '@arco-design/web-vue/es/icon';
|
import { IconDown } from '@arco-design/web-vue/es/icon';
|
||||||
import { Message, Modal } from '@arco-design/web-vue';
|
import { Message, Modal } from '@arco-design/web-vue';
|
||||||
import HandsontablePro from '@/components/HandsontablePro/index.vue';
|
import HandsontablePro from '@/components/HandsontablePro/index.vue';
|
||||||
import PaginationPro from '@/components/PaginationPro/index.vue';
|
import PaginationPro from '@/components/PaginationPro/index.vue';
|
||||||
@ -183,7 +183,31 @@
|
|||||||
const selectedRowCount = ref(0);
|
const selectedRowCount = ref(0);
|
||||||
/** 表格选中行(用于批量操作) */
|
/** 表格选中行(用于批量操作) */
|
||||||
const selectedOrderRows = ref<any[]>([]);
|
const selectedOrderRows = ref<any[]>([]);
|
||||||
const actionSubmitting = ref(false);
|
/** 当前正在执行的 toolbar 操作(仅对应按钮显示 loading,避免全部按钮联动) */
|
||||||
|
const actionLoadingKey = ref<string | null>(null);
|
||||||
|
|
||||||
|
function isActionLoading(key: string) {
|
||||||
|
return actionLoadingKey.value === key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionBusy(exceptKey?: string) {
|
||||||
|
const current = actionLoadingKey.value;
|
||||||
|
return current != null && current !== exceptKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withOrderActionLoading<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
||||||
|
if (actionLoadingKey.value) {
|
||||||
|
return Promise.reject(new Error('busy'));
|
||||||
|
}
|
||||||
|
actionLoadingKey.value = key;
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} finally {
|
||||||
|
if (actionLoadingKey.value === key) {
|
||||||
|
actionLoadingKey.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const printConnectionState = useHiprintClientState();
|
const printConnectionState = useHiprintClientState();
|
||||||
const singlePrintVisible = ref(false);
|
const singlePrintVisible = ref(false);
|
||||||
const batchPrintVisible = ref(false);
|
const batchPrintVisible = ref(false);
|
||||||
@ -217,9 +241,8 @@
|
|||||||
const handleCreateTrackOrder = async () => {
|
const handleCreateTrackOrder = async () => {
|
||||||
const rows = requireSelectedRows();
|
const rows = requireSelectedRows();
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
actionSubmitting.value = true;
|
await withOrderActionLoading('track', async () => {
|
||||||
const resultBlocks: string[] = [];
|
const resultBlocks: string[] = [];
|
||||||
try {
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const id = getOrderRowId(row)!;
|
const id = getOrderRowId(row)!;
|
||||||
const waybillNo = row.waybillNo || row.serviceNo || String(id);
|
const waybillNo = row.waybillNo || row.serviceNo || String(id);
|
||||||
@ -232,9 +255,7 @@
|
|||||||
}
|
}
|
||||||
trackUpsertResultText.value = resultBlocks.join('\n\n');
|
trackUpsertResultText.value = resultBlocks.join('\n\n');
|
||||||
trackUpsertModalVisible.value = true;
|
trackUpsertModalVisible.value = true;
|
||||||
} finally {
|
}).catch(() => undefined);
|
||||||
actionSubmitting.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDownstreamForwardResult(
|
function formatDownstreamForwardResult(
|
||||||
@ -263,9 +284,8 @@
|
|||||||
const handleForwardDownstreamCreate = async () => {
|
const handleForwardDownstreamCreate = async () => {
|
||||||
const rows = requireSelectedRows();
|
const rows = requireSelectedRows();
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
actionSubmitting.value = true;
|
await withOrderActionLoading('downstream', async () => {
|
||||||
const resultBlocks: string[] = [];
|
const resultBlocks: string[] = [];
|
||||||
try {
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const id = getOrderRowId(row)!;
|
const id = getOrderRowId(row)!;
|
||||||
const waybillNo = row.waybillNo || row.serviceNo || String(id);
|
const waybillNo = row.waybillNo || row.serviceNo || String(id);
|
||||||
@ -278,9 +298,7 @@
|
|||||||
}
|
}
|
||||||
downstreamForwardResultText.value = resultBlocks.join('\n\n');
|
downstreamForwardResultText.value = resultBlocks.join('\n\n');
|
||||||
downstreamForwardModalVisible.value = true;
|
downstreamForwardModalVisible.value = true;
|
||||||
} finally {
|
}).catch(() => undefined);
|
||||||
actionSubmitting.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getOrderRowId(row: any): number | string | undefined {
|
function getOrderRowId(row: any): number | string | undefined {
|
||||||
@ -350,69 +368,73 @@
|
|||||||
const handlePay = async () => {
|
const handlePay = async () => {
|
||||||
const rows = requireSelectedRows();
|
const rows = requireSelectedRows();
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
for (const row of rows) {
|
await withOrderActionLoading('pay', async () => {
|
||||||
await postOrderPay({ id: getOrderRowId(row)! });
|
for (const row of rows) {
|
||||||
}
|
await postOrderPay({ id: getOrderRowId(row)! });
|
||||||
Message.success('支付请求已提交');
|
}
|
||||||
notifyCustomerBalanceChanged();
|
Message.success('支付请求已提交');
|
||||||
await refreshListAndTabCounts();
|
notifyCustomerBalanceChanged();
|
||||||
|
await refreshListAndTabCounts();
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '操作失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '操作失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePreDeclare = async () => {
|
const handlePreDeclare = async () => {
|
||||||
const rows = requireSelectedRows();
|
const rows = requireSelectedRows();
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
for (const row of rows) {
|
await withOrderActionLoading('predeclare', async () => {
|
||||||
await postOrderPreDeclare({ id: getOrderRowId(row)! });
|
for (const row of rows) {
|
||||||
}
|
await postOrderPreDeclare({ id: getOrderRowId(row)! });
|
||||||
Message.success('预报已提交');
|
}
|
||||||
await refreshListAndTabCounts();
|
Message.success('预报已提交');
|
||||||
|
await refreshListAndTabCounts();
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '操作失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '操作失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIntercept = async () => {
|
const handleIntercept = async () => {
|
||||||
const rows = requireSelectedRows();
|
const rows = requireSelectedRows();
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
for (const row of rows) {
|
await withOrderActionLoading('intercept', async () => {
|
||||||
await postOrderIntercept({ id: getOrderRowId(row)! });
|
for (const row of rows) {
|
||||||
}
|
await postOrderIntercept({ id: getOrderRowId(row)! });
|
||||||
Message.success('拦截已提交');
|
}
|
||||||
await refreshListAndTabCounts();
|
Message.success('拦截已提交');
|
||||||
|
await refreshListAndTabCounts();
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '操作失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '操作失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancelIntercept = async () => {
|
const handleCancelIntercept = async () => {
|
||||||
const rows = requireSelectedRows();
|
const rows = requireSelectedRows();
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
for (const row of rows) {
|
await withOrderActionLoading('cancelIntercept', async () => {
|
||||||
await postOrderCancelIntercept({ id: getOrderRowId(row)! });
|
for (const row of rows) {
|
||||||
}
|
await postOrderCancelIntercept({ id: getOrderRowId(row)! });
|
||||||
Message.success('已取消拦截');
|
}
|
||||||
await refreshListAndTabCounts();
|
Message.success('已取消拦截');
|
||||||
|
await refreshListAndTabCounts();
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '操作失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '操作失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -428,18 +450,19 @@
|
|||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: `确定删除选中的 ${rows.length} 条订单吗?`,
|
content: `确定删除选中的 ${rows.length} 条订单吗?`,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
for (const row of rows) {
|
await withOrderActionLoading('delete', async () => {
|
||||||
await postOrderDelete({ id: getOrderRowId(row)! });
|
for (const row of rows) {
|
||||||
}
|
await postOrderDelete({ id: getOrderRowId(row)! });
|
||||||
Message.success('已删除');
|
}
|
||||||
await refreshListAndTabCounts();
|
Message.success('已删除');
|
||||||
|
await refreshListAndTabCounts();
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '删除失败');
|
if (e?.message !== 'busy') {
|
||||||
|
Message.error(e?.message || '删除失败');
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
|
||||||
actionSubmitting.value = false;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -471,21 +494,22 @@
|
|||||||
Message.warning('请先在顶部打开并连接打印开关');
|
Message.warning('请先在顶部打开并连接打印开关');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
const row = rows[0];
|
await withOrderActionLoading('singlePrint', async () => {
|
||||||
const { printUrl } = await postOrderPrint({ id: getOrderRowId(row)! });
|
const row = rows[0];
|
||||||
if (!isUsablePrintUrl(printUrl)) {
|
const { printUrl } = await postOrderPrint({ id: getOrderRowId(row)! });
|
||||||
Message.info('未获取到打印文件,已打开打印弹窗供预览布局');
|
if (!isUsablePrintUrl(printUrl)) {
|
||||||
|
Message.info('未获取到打印文件,已打开打印弹窗供预览布局');
|
||||||
|
singlePrintVisible.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
singlePrintItems.value = [buildPrintItem(row, printUrl)];
|
||||||
singlePrintVisible.value = true;
|
singlePrintVisible.value = true;
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
singlePrintItems.value = [buildPrintItem(row, printUrl)];
|
|
||||||
singlePrintVisible.value = true;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '打印失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '打印失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -498,45 +522,47 @@
|
|||||||
Message.warning('请先在顶部打开并连接打印开关');
|
Message.warning('请先在顶部打开并连接打印开关');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
const items: Array<{ id: number | string; label: string; url: string }> = [];
|
await withOrderActionLoading('batchPrint', async () => {
|
||||||
for (const row of rows) {
|
const items: Array<{ id: number | string; label: string; url: string }> = [];
|
||||||
const { printUrl } = await postOrderPrint({ id: getOrderRowId(row)! });
|
for (const row of rows) {
|
||||||
if (isUsablePrintUrl(printUrl)) {
|
const { printUrl } = await postOrderPrint({ id: getOrderRowId(row)! });
|
||||||
items.push(buildPrintItem(row, printUrl));
|
if (isUsablePrintUrl(printUrl)) {
|
||||||
|
items.push(buildPrintItem(row, printUrl));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (!items.length) {
|
||||||
if (!items.length) {
|
Message.info('未获取到可打印文件,已打开打印弹窗供预览布局');
|
||||||
Message.info('未获取到可打印文件,已打开打印弹窗供预览布局');
|
batchPrintVisible.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
batchPrintItems.value = items;
|
||||||
batchPrintVisible.value = true;
|
batchPrintVisible.value = true;
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
batchPrintItems.value = items;
|
|
||||||
batchPrintVisible.value = true;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '批量打印失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '批量打印失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
actionSubmitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
const status = activeTab.value === ORDER_STATUS_ALL ? ORDER_STATUS_ALL : activeTab.value;
|
await withOrderActionLoading('export', async () => {
|
||||||
const { downloadUrl } = await postOrderExport({
|
const status = activeTab.value === ORDER_STATUS_ALL ? ORDER_STATUS_ALL : activeTab.value;
|
||||||
status,
|
const { downloadUrl } = await postOrderExport({
|
||||||
keyword: '',
|
status,
|
||||||
createTimeStart: '',
|
keyword: '',
|
||||||
createTimeEnd: '',
|
createTimeStart: '',
|
||||||
|
createTimeEnd: '',
|
||||||
|
});
|
||||||
|
if (downloadUrl) window.open(downloadUrl, '_blank');
|
||||||
|
Message.success(downloadUrl ? '导出链接已打开' : '导出已提交');
|
||||||
});
|
});
|
||||||
if (downloadUrl) window.open(downloadUrl, '_blank');
|
|
||||||
Message.success(downloadUrl ? '导出链接已打开' : '导出已提交');
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || '导出失败');
|
if (e?.message !== 'busy') {
|
||||||
} finally {
|
Message.error(e?.message || '导出失败');
|
||||||
actionSubmitting.value = false;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -675,15 +701,46 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions-right">
|
<div class="actions-right">
|
||||||
<a-button class="btn-pay" :loading="actionSubmitting" @click="handlePay">{{ t('order.myOrders.actions.pay') }}</a-button>
|
<a-button
|
||||||
<a-button class="btn-predeclare" :loading="actionSubmitting" @click="handlePreDeclare">{{ t('order.myOrders.actions.predeclare') }}</a-button>
|
class="btn-pay"
|
||||||
<a-button class="btn-track" :loading="actionSubmitting" @click="handleCreateTrackOrder">{{ t('order.myOrders.actions.createTrackOrder') }}</a-button>
|
:loading="isActionLoading('pay')"
|
||||||
<a-button class="btn-downstream" :loading="actionSubmitting" @click="handleForwardDownstreamCreate">{{ t('order.myOrders.actions.forwardDownstreamCreate') }}</a-button>
|
:disabled="isActionBusy('pay')"
|
||||||
<a-button class="btn-print" :loading="actionSubmitting" @click="handleSinglePrint">单个打印</a-button>
|
@click="handlePay"
|
||||||
<a-button class="btn-print" :loading="actionSubmitting" @click="handleBatchPrint">批量打印</a-button>
|
>{{ t('order.myOrders.actions.pay') }}</a-button>
|
||||||
<a-dropdown trigger="click" position="bl">
|
<a-button
|
||||||
<a-button class="btn-settings">
|
type="primary"
|
||||||
<icon-settings />
|
:loading="isActionLoading('predeclare')"
|
||||||
|
:disabled="isActionBusy('predeclare')"
|
||||||
|
@click="handlePreDeclare"
|
||||||
|
>{{ t('order.myOrders.actions.predeclare') }}</a-button>
|
||||||
|
<a-button
|
||||||
|
class="btn-track"
|
||||||
|
:loading="isActionLoading('track')"
|
||||||
|
:disabled="isActionBusy('track')"
|
||||||
|
@click="handleCreateTrackOrder"
|
||||||
|
>{{ t('order.myOrders.actions.createTrackOrder') }}</a-button>
|
||||||
|
<a-button
|
||||||
|
class="btn-downstream"
|
||||||
|
:loading="isActionLoading('downstream')"
|
||||||
|
:disabled="isActionBusy('downstream')"
|
||||||
|
@click="handleForwardDownstreamCreate"
|
||||||
|
>{{ t('order.myOrders.actions.forwardDownstreamCreate') }}</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="isActionLoading('singlePrint')"
|
||||||
|
:disabled="isActionBusy('singlePrint')"
|
||||||
|
@click="handleSinglePrint"
|
||||||
|
>单个打印</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="isActionLoading('batchPrint')"
|
||||||
|
:disabled="isActionBusy('batchPrint')"
|
||||||
|
@click="handleBatchPrint"
|
||||||
|
>批量打印</a-button>
|
||||||
|
<a-dropdown trigger="click" position="bl" :disabled="!!actionLoadingKey">
|
||||||
|
<a-button type="text" class="btn-more" :disabled="!!actionLoadingKey">
|
||||||
|
{{ t('order.myOrders.actions.more') }}
|
||||||
|
<icon-down class="btn-more-arrow" />
|
||||||
</a-button>
|
</a-button>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption @click="onMoreActionClick('intercept')">{{ t('order.myOrders.actions.intercept') }}</a-doption>
|
<a-doption @click="onMoreActionClick('intercept')">{{ t('order.myOrders.actions.intercept') }}</a-doption>
|
||||||
@ -850,204 +907,78 @@
|
|||||||
|
|
||||||
.actions-right {
|
.actions-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: var(--app-button-gap, 8px);
|
gap: var(--app-button-gap, 8px);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px 0;
|
padding: 12px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
.arco-btn {
|
:deep(.arco-btn) {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-weight: 500;
|
font-size: 14px;
|
||||||
transition: all 0.3s ease;
|
font-weight: 400;
|
||||||
border: 2px solid;
|
line-height: 30px;
|
||||||
position: relative;
|
}
|
||||||
overflow: hidden;
|
|
||||||
padding: 8px 16px;
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
// 支付按钮 - 淡黄色
|
:deep(.arco-btn.btn-pay) {
|
||||||
&.btn-pay {
|
color: #d46b08;
|
||||||
background: #fffbe6;
|
background: #fffbe6;
|
||||||
border-color: #ffd666;
|
border-color: #ffd666;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
color: #d46b08;
|
color: #d46b08;
|
||||||
|
background: #fff7d1;
|
||||||
|
border-color: #ffc53d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
:deep(.arco-btn.btn-track),
|
||||||
background: #fff7d1;
|
:deep(.arco-btn.btn-downstream) {
|
||||||
border-color: #ffc53d;
|
color: #fff;
|
||||||
}
|
background: #000;
|
||||||
|
border-color: #000;
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: #fff1b8;
|
color: #fff;
|
||||||
border-color: #ffb020;
|
background: #262626;
|
||||||
}
|
border-color: #262626;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预报按钮 - 主题色
|
&:active:not(:disabled) {
|
||||||
&.btn-predeclare {
|
color: #fff;
|
||||||
background: var(--app-primary-color);
|
background: #141414;
|
||||||
border-color: var(--app-primary-color);
|
border-color: #141414;
|
||||||
color: #ffffff;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
:deep(.arco-btn.btn-more) {
|
||||||
background: var(--app-primary-color-hover);
|
height: 32px;
|
||||||
border-color: var(--app-primary-color-hover);
|
padding: 0 4px;
|
||||||
}
|
color: #4e5969;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
.btn-more-arrow {
|
||||||
background: var(--app-primary-color-hover);
|
font-size: 12px;
|
||||||
border-color: var(--app-primary-color-hover);
|
margin-left: 2px;
|
||||||
}
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-track {
|
&:hover:not(:disabled) {
|
||||||
background: #fff;
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: var(--app-primary-color);
|
color: var(--app-primary-color);
|
||||||
|
background: transparent !important;
|
||||||
&:hover:not(:disabled) {
|
border: none !important;
|
||||||
background: rgba(var(--primary-6), 0.06);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-downstream {
|
&:active:not(:disabled) {
|
||||||
background: #f6ffed;
|
color: var(--app-primary-color-hover);
|
||||||
border-color: #95de64;
|
background: transparent !important;
|
||||||
color: #389e0d;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: #d9f7be;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拦截按钮 - 主题色
|
|
||||||
&.btn-intercept {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消拦截按钮 - 主题色
|
|
||||||
&.btn-cancel-intercept {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除按钮 - 主题色
|
|
||||||
&.btn-delete {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印按钮 - 主题色
|
|
||||||
&.btn-print {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出按钮 - 主题色
|
|
||||||
&.btn-export {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改订单按钮 - 主题色
|
|
||||||
&.btn-modify {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置按钮 - 主题色
|
|
||||||
&.btn-settings {
|
|
||||||
background: var(--app-primary-color);
|
|
||||||
border-color: var(--app-primary-color);
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 8px 12px;
|
|
||||||
min-width: 36px;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: var(--app-primary-color-hover);
|
|
||||||
border-color: var(--app-primary-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 禁用状态
|
|
||||||
&:disabled {
|
|
||||||
opacity: 1;
|
|
||||||
cursor: not-allowed;
|
|
||||||
background: rgba(169, 169, 169, 0.8) !important;
|
|
||||||
border-color: rgba(169, 169, 169, 0.8) !important;
|
|
||||||
color: #ffffff !important;
|
|
||||||
transform: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -108,8 +108,6 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
/>
|
/>
|
||||||
<a-button size="small" @click="expandAllPermissions">{{ t('system.role.permission.expandAll') }}</a-button>
|
|
||||||
<a-button size="small" @click="collapseAllPermissions">{{ t('system.role.permission.collapseAll') }}</a-button>
|
|
||||||
<a-button size="small" @click="checkAllPermissions">{{ t('system.role.permission.checkAll') }}</a-button>
|
<a-button size="small" @click="checkAllPermissions">{{ t('system.role.permission.checkAll') }}</a-button>
|
||||||
<a-button size="small" @click="uncheckAllPermissions">{{ t('system.role.permission.uncheckAll') }}</a-button>
|
<a-button size="small" @click="uncheckAllPermissions">{{ t('system.role.permission.uncheckAll') }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
@ -117,16 +115,66 @@
|
|||||||
{{ t('system.role.permission.selectedCount', { selected: selectedPermissions.length, total: totalPermissionCount }) }}
|
{{ t('system.role.permission.selectedCount', { selected: selectedPermissions.length, total: totalPermissionCount }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="section-card permission-tree-card">
|
<div class="section-card permission-list-card">
|
||||||
<a-tree
|
<a-table
|
||||||
v-if="filteredPermissionTreeData.length > 0"
|
v-if="filteredPermissionRows.length > 0"
|
||||||
v-model:checked-keys="selectedPermissions"
|
:data="filteredPermissionRows"
|
||||||
v-model:expanded-keys="permissionExpandedKeys"
|
:pagination="false"
|
||||||
checkable
|
:bordered="{ cell: true }"
|
||||||
:data="filteredPermissionTreeData"
|
:span-method="permissionSpanMethod"
|
||||||
:field-names="{ title: 'name', key: 'code', children: 'children' }"
|
row-key="rowKey"
|
||||||
check-strictly
|
size="small"
|
||||||
/>
|
:scroll="{ y: permissionTableScrollY }"
|
||||||
|
>
|
||||||
|
<template #columns>
|
||||||
|
<a-table-column
|
||||||
|
:title="t('system.role.permission.col.level1')"
|
||||||
|
data-index="level1Name"
|
||||||
|
:width="180"
|
||||||
|
>
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-checkbox
|
||||||
|
:model-value="isPermissionChecked(record.level1Code)"
|
||||||
|
@change="(checked) => togglePermission(record.level1Code, checked)"
|
||||||
|
>
|
||||||
|
{{ record.level1Name }}
|
||||||
|
</a-checkbox>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column
|
||||||
|
:title="t('system.role.permission.col.level2')"
|
||||||
|
data-index="level2Name"
|
||||||
|
:width="200"
|
||||||
|
>
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-checkbox
|
||||||
|
:model-value="isPermissionChecked(record.level2Code)"
|
||||||
|
@change="(checked) => togglePermission(record.level2Code, checked)"
|
||||||
|
>
|
||||||
|
{{ record.level2Name }}
|
||||||
|
</a-checkbox>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column :title="t('system.role.permission.col.level3')">
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-checkbox-group
|
||||||
|
v-if="record.level2HasChildren"
|
||||||
|
v-model="selectedPermissions"
|
||||||
|
class="permission-button-group"
|
||||||
|
>
|
||||||
|
<a-checkbox
|
||||||
|
v-for="btn in record.buttons"
|
||||||
|
:key="btn.code"
|
||||||
|
:value="btn.code"
|
||||||
|
>
|
||||||
|
{{ btn.name }}
|
||||||
|
</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
<span v-else class="permission-no-buttons">—</span>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
<div v-else class="permission-empty">
|
<div v-else class="permission-empty">
|
||||||
{{ permissionKeyword ? t('system.role.permission.noMatch') : t('system.role.permission.loading') }}
|
{{ permissionKeyword ? t('system.role.permission.noMatch') : t('system.role.permission.loading') }}
|
||||||
</div>
|
</div>
|
||||||
@ -151,12 +199,17 @@ import type {
|
|||||||
RoleCreateDto,
|
RoleCreateDto,
|
||||||
RoleUpdateDto,
|
RoleUpdateDto,
|
||||||
PermissionGroupDto,
|
PermissionGroupDto,
|
||||||
PermissionDefinitionDto,
|
|
||||||
} from '@/api/rbac'
|
} from '@/api/rbac'
|
||||||
import { useWarehouseList } from '@/composables/useWarehouseList'
|
import { useWarehouseList } from '@/composables/useWarehouseList'
|
||||||
import { hasPermission } from '@/utils/rbac-permission'
|
import { hasPermission } from '@/utils/rbac-permission'
|
||||||
import { handleApiError } from '@/utils/rbac-error-handler'
|
import { handleApiError } from '@/utils/rbac-error-handler'
|
||||||
import { buildFlatLeftMenuTree, findLeftMenuItemByKey } from '@/utils/rbacLeftMenuTree'
|
import { buildFlatLeftMenuTree, findLeftMenuItemByKey } from '@/utils/rbacLeftMenuTree'
|
||||||
|
import {
|
||||||
|
buildPermissionAssignRows,
|
||||||
|
collectPermissionCodesFromRows,
|
||||||
|
computeLevel1RowSpans,
|
||||||
|
filterPermissionAssignRows,
|
||||||
|
} from '@/utils/rbacPermissionAssignRows'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -192,7 +245,7 @@ const permissionLoading = ref(false)
|
|||||||
const permissionSaving = ref(false)
|
const permissionSaving = ref(false)
|
||||||
const permissionTree = ref<PermissionGroupDto[]>([])
|
const permissionTree = ref<PermissionGroupDto[]>([])
|
||||||
const selectedPermissions = ref<string[]>([])
|
const selectedPermissions = ref<string[]>([])
|
||||||
const permissionExpandedKeys = ref<string[]>([])
|
const permissionTableScrollY = 'calc(100vh - 340px)'
|
||||||
|
|
||||||
const internalRoles = computed(() =>
|
const internalRoles = computed(() =>
|
||||||
dataSource.value.filter((role) => {
|
dataSource.value.filter((role) => {
|
||||||
@ -212,24 +265,16 @@ const selectedKeys = computed(() =>
|
|||||||
selectedRole.value?.id ? [`item-${selectedRole.value.id}`] : []
|
selectedRole.value?.id ? [`item-${selectedRole.value.id}`] : []
|
||||||
)
|
)
|
||||||
|
|
||||||
const permissionTreeData = computed(() => {
|
const permissionRows = computed(() => buildPermissionAssignRows(permissionTree.value))
|
||||||
const treeData: PermissionDefinitionDto[] = []
|
|
||||||
permissionTree.value.forEach((group: PermissionGroupDto) => {
|
|
||||||
if (group.permissions?.length) {
|
|
||||||
treeData.push({
|
|
||||||
code: group.code || '',
|
|
||||||
name: group.name || '',
|
|
||||||
children: group.permissions || [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return treeData
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalPermissionCount = computed(() => countLeafCodes(permissionTreeData.value))
|
const filteredPermissionRows = computed(() =>
|
||||||
|
filterPermissionAssignRows(permissionRows.value, permissionKeyword.value)
|
||||||
|
)
|
||||||
|
|
||||||
const filteredPermissionTreeData = computed(() =>
|
const level1RowSpans = computed(() => computeLevel1RowSpans(filteredPermissionRows.value))
|
||||||
filterPermissionTree(permissionTreeData.value, permissionKeyword.value.trim())
|
|
||||||
|
const totalPermissionCount = computed(() =>
|
||||||
|
collectPermissionCodesFromRows(permissionRows.value).length
|
||||||
)
|
)
|
||||||
|
|
||||||
const roleTags = computed((): RbacDetailTag[] => {
|
const roleTags = computed((): RbacDetailTag[] => {
|
||||||
@ -261,38 +306,30 @@ const roleTabs = computed(() => {
|
|||||||
return tabs
|
return tabs
|
||||||
})
|
})
|
||||||
|
|
||||||
function countLeafCodes(nodes: PermissionDefinitionDto[]): number {
|
function permissionSpanMethod({
|
||||||
let count = 0
|
rowIndex,
|
||||||
nodes.forEach((node) => {
|
columnIndex,
|
||||||
if (node.children?.length) {
|
}: {
|
||||||
count += countLeafCodes(node.children)
|
rowIndex: number
|
||||||
} else if (node.code) {
|
columnIndex: number
|
||||||
count += 1
|
}) {
|
||||||
}
|
if (columnIndex !== 0) return { rowspan: 1, colspan: 1 }
|
||||||
})
|
const rowspan = level1RowSpans.value[rowIndex] ?? 1
|
||||||
return count
|
if (rowspan === 0) return { rowspan: 0, colspan: 0 }
|
||||||
|
return { rowspan, colspan: 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterPermissionTree(
|
function isPermissionChecked(code: string) {
|
||||||
nodes: PermissionDefinitionDto[],
|
return selectedPermissions.value.includes(code)
|
||||||
keyword: string
|
}
|
||||||
): PermissionDefinitionDto[] {
|
|
||||||
if (!keyword) return nodes
|
function togglePermission(code: string, checked: boolean | (string | number | boolean)) {
|
||||||
const lower = keyword.toLowerCase()
|
if (!code) return
|
||||||
const result: PermissionDefinitionDto[] = []
|
const enabled = checked === true
|
||||||
nodes.forEach((node) => {
|
const next = new Set(selectedPermissions.value)
|
||||||
const children = node.children?.length ? filterPermissionTree(node.children, keyword) : []
|
if (enabled) next.add(code)
|
||||||
const selfMatch =
|
else next.delete(code)
|
||||||
(node.name || '').toLowerCase().includes(lower) ||
|
selectedPermissions.value = Array.from(next)
|
||||||
(node.code || '').toLowerCase().includes(lower)
|
|
||||||
if (selfMatch || children.length) {
|
|
||||||
result.push({
|
|
||||||
...node,
|
|
||||||
children: children.length ? children : selfMatch ? node.children : undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTime(value?: string | null) {
|
function formatTime(value?: string | null) {
|
||||||
@ -365,7 +402,6 @@ async function loadRolePermissions(roleId: string) {
|
|||||||
try {
|
try {
|
||||||
await loadPermissionTree()
|
await loadPermissionTree()
|
||||||
selectedPermissions.value = (await RoleService.getRbacRoleGetRolePermissions(roleId)) || []
|
selectedPermissions.value = (await RoleService.getRbacRoleGetRolePermissions(roleId)) || []
|
||||||
permissionExpandedKeys.value = expandAllCodes(permissionTreeData.value)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error)
|
handleApiError(error)
|
||||||
} finally {
|
} finally {
|
||||||
@ -373,13 +409,12 @@ async function loadRolePermissions(roleId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandAllCodes(nodes: PermissionDefinitionDto[]): string[] {
|
function checkAllPermissions() {
|
||||||
const keys: string[] = []
|
selectedPermissions.value = collectPermissionCodesFromRows(permissionRows.value)
|
||||||
nodes.forEach((node) => {
|
}
|
||||||
if (node.code) keys.push(node.code)
|
|
||||||
if (node.children?.length) keys.push(...expandAllCodes(node.children))
|
function uncheckAllPermissions() {
|
||||||
})
|
selectedPermissions.value = []
|
||||||
return keys
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
@ -466,22 +501,6 @@ async function handleSavePermissions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandAllPermissions() {
|
|
||||||
permissionExpandedKeys.value = expandAllCodes(filteredPermissionTreeData.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function collapseAllPermissions() {
|
|
||||||
permissionExpandedKeys.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkAllPermissions() {
|
|
||||||
selectedPermissions.value = expandAllCodes(permissionTreeData.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function uncheckAllPermissions() {
|
|
||||||
selectedPermissions.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => selectedRole.value?.id,
|
() => selectedRole.value?.id,
|
||||||
async (roleId) => {
|
async (roleId) => {
|
||||||
@ -494,12 +513,6 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(permissionKeyword, (kw) => {
|
|
||||||
if (kw.trim() && activeTab.value === 'permissions') {
|
|
||||||
permissionExpandedKeys.value = expandAllCodes(filteredPermissionTreeData.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadData()
|
await loadData()
|
||||||
await loadPermissionTree()
|
await loadPermissionTree()
|
||||||
@ -519,10 +532,31 @@ onMounted(async () => {
|
|||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission-tree-card {
|
.permission-list-card {
|
||||||
min-height: 320px;
|
min-height: 320px;
|
||||||
max-height: calc(100vh - 340px);
|
}
|
||||||
overflow: auto;
|
|
||||||
|
.permission-button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 16px;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-no-buttons {
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-list-card :deep(.arco-checkbox) {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-list-card :deep(.arco-table-th) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-list-card :deep(.arco-table-td) {
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.perm-summary {
|
.perm-summary {
|
||||||
|
|||||||
@ -264,7 +264,7 @@ function goRecharge() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goRechargeRecord() {
|
function goRechargeRecord() {
|
||||||
router.push({ name: 'VipRechargeRechargeRecord' })
|
router.push({ name: 'FinanceRechargeRecord' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function goMyOrders() {
|
function goMyOrders() {
|
||||||
|
|||||||
@ -280,7 +280,7 @@ const reset = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const goToRecord = () => {
|
const goToRecord = () => {
|
||||||
router.push({ name: 'VipRechargeRechargeRecord' });
|
router.push({ name: 'FinanceRechargeRecord' });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -100,6 +100,26 @@ export default defineConfig(({ mode }) => {
|
|||||||
target: 'http://api.nanafa.com/admin',
|
target: 'http://api.nanafa.com/admin',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
|
bypass(req) {
|
||||||
|
const path = req.url?.split('?')[0] ?? '';
|
||||||
|
const systemSpaExact = ['/system/dict', '/system/warehouseAddress', '/system/announcement'];
|
||||||
|
if (systemSpaExact.some((p) => path === p || path === `${p}/`)) {
|
||||||
|
return '/index.html';
|
||||||
|
}
|
||||||
|
const systemSpaPrefixes = [
|
||||||
|
'/system/users',
|
||||||
|
'/system/roles',
|
||||||
|
'/system/menus',
|
||||||
|
'/system/organizations',
|
||||||
|
'/system/batchEmail',
|
||||||
|
'/system/otherConfig',
|
||||||
|
'/system/xhsAiAssistant',
|
||||||
|
'/system/dataPerm',
|
||||||
|
];
|
||||||
|
if (systemSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) {
|
||||||
|
return '/index.html';
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// 小红书 OMS-Xhs 接口(开发默认走本机 Admin API,与桌面 Agent 一致)
|
// 小红书 OMS-Xhs 接口(开发默认走本机 Admin API,与桌面 Agent 一致)
|
||||||
'/xhs': {
|
'/xhs': {
|
||||||
@ -265,9 +285,14 @@ export default defineConfig(({ mode }) => {
|
|||||||
'/system/organizations',
|
'/system/organizations',
|
||||||
'/system/batchEmail',
|
'/system/batchEmail',
|
||||||
'/system/xhsAiAssistant',
|
'/system/xhsAiAssistant',
|
||||||
|
'/system/otherConfig',
|
||||||
|
'/system/dataPerm',
|
||||||
// User 相关前端路由
|
// User 相关前端路由
|
||||||
'/user/recharge',
|
'/user/recharge',
|
||||||
'/user/rechargeRecord',
|
// Finance 相关前端路由
|
||||||
|
'/finance/rechargeRecord',
|
||||||
|
'/finance/accountFlow',
|
||||||
|
'/finance/receivablePayable',
|
||||||
];
|
];
|
||||||
|
|
||||||
// 直接在 configureServer 中设置中间件,确保在代理之前执行
|
// 直接在 configureServer 中设置中间件,确保在代理之前执行
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user