fix platform authorization and workflow integrity
This commit is contained in:
@@ -33,6 +33,7 @@ export type DetailAction = {
|
||||
method?: 'POST' | 'PUT' | 'PATCH';
|
||||
danger?: boolean;
|
||||
fields?: ResourceField[];
|
||||
visibleFor?: { field: string; values: Array<string | number> };
|
||||
};
|
||||
|
||||
export type ResourceUiDefinition = {
|
||||
@@ -303,24 +304,24 @@ export const resources: ResourceUiDefinition[] = [
|
||||
define('product_owner', '智能气阀归属记录', 'readonly', []),
|
||||
|
||||
define('gasorder_contract', '配送合同', 'managed', [f('contract_no', { required: true }), relation('user_account_identity', '/user_account', true), relation('gas_basic_identity', '/gas_basic', true), relation('delivery_basic_identity', '/delivery_basic'), f('title', { required: true }), f('terms'), f('file_uri'), f('default_delivery_fee'), f('signed_at', { required: true }), f('effective_at', { required: true }), f('expired_at')], 'list', [
|
||||
{ name: '启用合同', resource: '/gasorder_contract/:identity/activate', fields: reason },
|
||||
{ name: '续签合同', resource: '/gasorder_contract/:identity/renew', fields: [f('effective_at', { required: true }), f('expired_at'), ...reason] },
|
||||
{ name: '终止合同', resource: '/gasorder_contract/:identity/terminate', danger: true, fields: reason },
|
||||
{ name: '启用合同', resource: '/gasorder_contract/:identity/activate', fields: reason, visibleFor: { field: 'contract_status', values: [10] } },
|
||||
{ name: '续签合同', resource: '/gasorder_contract/:identity/renew', fields: [f('effective_at', { required: true }), f('expired_at'), ...reason], visibleFor: { field: 'contract_status', values: [11, 12] } },
|
||||
{ name: '终止合同', resource: '/gasorder_contract/:identity/terminate', danger: true, fields: reason, visibleFor: { field: 'contract_status', values: [11] } },
|
||||
], { canCreate: true, canEdit: true }),
|
||||
define('gasorder_contract_product', '合同气瓶', 'append_only', [relation('gasorder_contract_identity', '/gasorder_contract', true), relation('product_info_identity', '/product_info', true), f('unit_price')], 'list', [
|
||||
{ name: '解绑气瓶', resource: '/gasorder_contract_product/:identity/unbind', danger: true, fields: reason },
|
||||
]),
|
||||
define('gasorder_contract_revision', '合同修订记录', 'readonly', []),
|
||||
define('gasorder_basic', '气体配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true), f('creator_type', { required: true, type: 'select', options: [{ label: '用户', value: 'user' }, { label: '工作人员', value: 'staff' }, { label: '配送站', value: 'delivery' }, { label: '气站', value: 'gas' }] }), f('creator_identity', { required: true }), relation('user_address_identity', '/user_address', true), f('gasorder_contract_product_identities', { required: true, type: 'identity-list', relation: '/gasorder_contract_product' }), f('contact_name', { required: true }), f('contact_phone', { required: true }), f('discount_amount'), f('remark')], 'list', [
|
||||
{ name: '分配订单', resource: '/gasorder_basic/:identity/assign', fields: [relation('delivery_basic_identity', '/delivery_basic', true), relation('staff_account_identity', '/staff_account', true), ...reason] },
|
||||
{ name: '开始罐装', resource: '/gasorder_basic/:identity/filling', fields: reason },
|
||||
{ name: '待配送', resource: '/gasorder_basic/:identity/ready', fields: reason },
|
||||
{ name: '开始配送', resource: '/gasorder_basic/:identity/delivering', fields: reason },
|
||||
{ name: '等待签收', resource: '/gasorder_basic/:identity/awaiting-confirmation', fields: reason },
|
||||
{ name: '完成订单', resource: '/gasorder_basic/:identity/complete', fields: [...reason, f('confirm_type', { required: true }), f('recipient_name', { required: true }), f('recipient_phone'), f('proof_uri'), f('remark')] },
|
||||
{ name: '标记异常', resource: '/gasorder_basic/:identity/exception', fields: reason },
|
||||
{ name: '恢复订单', resource: '/gasorder_basic/:identity/recover', fields: reason },
|
||||
{ name: '取消订单', resource: '/gasorder_basic/:identity/cancel', danger: true, fields: reason },
|
||||
{ name: '分配订单', resource: '/gasorder_basic/:identity/assign', fields: [relation('delivery_basic_identity', '/delivery_basic', true), relation('staff_account_identity', '/staff_account', true), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } },
|
||||
{ name: '开始罐装', resource: '/gasorder_basic/:identity/filling', fields: reason, visibleFor: { field: 'order_status', values: [18] } },
|
||||
{ name: '待配送', resource: '/gasorder_basic/:identity/ready', fields: reason, visibleFor: { field: 'order_status', values: [19] } },
|
||||
{ name: '开始配送', resource: '/gasorder_basic/:identity/delivering', fields: reason, visibleFor: { field: 'order_status', values: [20] } },
|
||||
{ name: '等待签收', resource: '/gasorder_basic/:identity/awaiting-confirmation', fields: reason, visibleFor: { field: 'order_status', values: [33] } },
|
||||
{ name: '完成订单', resource: '/gasorder_basic/:identity/complete', fields: [...reason, f('confirm_type', { required: true }), f('recipient_name', { required: true }), f('recipient_phone'), f('proof_uri'), f('remark')], visibleFor: { field: 'order_status', values: [34] } },
|
||||
{ name: '标记异常', resource: '/gasorder_basic/:identity/exception', fields: reason, visibleFor: { field: 'order_status', values: [19, 20, 33, 34] } },
|
||||
{ name: '恢复订单', resource: '/gasorder_basic/:identity/recover', fields: reason, visibleFor: { field: 'order_status', values: [21] } },
|
||||
{ name: '取消订单', resource: '/gasorder_basic/:identity/cancel', danger: true, fields: reason, visibleFor: { field: 'order_status', values: [16, 18] } },
|
||||
]),
|
||||
define('gasorder_item', '订单明细', 'readonly', []),
|
||||
define('gasorder_assign', '分配记录', 'readonly', []),
|
||||
@@ -349,7 +350,7 @@ export const resources: ResourceUiDefinition[] = [
|
||||
define('wallet_refund', '退款记录', 'readonly', []),
|
||||
define('wallet_apply_cash', '提现记录', 'readonly', [
|
||||
f('cash_no'),
|
||||
relation('wallet_basic_identity', '/wallet_basic'),
|
||||
f('wallet_basic_identity', { type: 'identity' }),
|
||||
f('amount'),
|
||||
f('apply_status', { type: 'select', options: [
|
||||
{ label: '待处理', value: 10 },
|
||||
@@ -365,8 +366,9 @@ export const resources: ResourceUiDefinition[] = [
|
||||
f('trade_no'),
|
||||
f('remark'),
|
||||
], 'list', [
|
||||
{ name: '审核通过', resource: '/wallet_apply_cash/:identity/approve', fields: reason },
|
||||
{ name: '审核驳回', resource: '/wallet_apply_cash/:identity/reject', danger: true, fields: reason },
|
||||
{ name: '审核通过', resource: '/wallet_apply_cash/:identity/approve', fields: reason, visibleFor: { field: 'apply_status', values: [10] } },
|
||||
{ name: '审核驳回', resource: '/wallet_apply_cash/:identity/reject', danger: true, fields: reason, visibleFor: { field: 'apply_status', values: [10] } },
|
||||
{ name: '标记处理完成', resource: '/wallet_apply_cash/:identity/complete', fields: [f('trade_no', { required: true }), f('callback_msg')], visibleFor: { field: 'apply_status', values: [25] } },
|
||||
]),
|
||||
|
||||
define('fin_payment', '财务支付记录', 'readonly', []),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -62,8 +62,8 @@ const routes: AppRouteRecordRaw[] = [
|
||||
group('organization', 'organization', '机构管理', 'icon-storage', 10, [
|
||||
child('organization', 'gas-basic', 'gas-basic', '气站管理', '/gas_basic', 'gas_basic'),
|
||||
child('organization', 'delivery-basic', 'delivery-basic', '配送点管理', '/delivery_basic', 'delivery_basic'),
|
||||
child('organization', 'gas-account', 'gas-account', '气站账户', '/gas_account', 'gas_basic', true, 'gas-basic'),
|
||||
child('organization', 'delivery-account', 'delivery-account', '配送点账户', '/delivery_account', 'delivery_basic', true, 'delivery-basic'),
|
||||
child('organization', 'gas-account', 'gas-account', '气站账户', '/gas_account', 'gas_basic', true, 'organization-gas-basic'),
|
||||
child('organization', 'delivery-account', 'delivery-account', '配送点账户', '/delivery_account', 'delivery_basic', true, 'organization-delivery-basic'),
|
||||
]),
|
||||
group('staff', 'staff', '工作人员管理', 'icon-user-group', 30, [
|
||||
{ ...child('staff', 'add', 'add', '新增工作人员', '/staff_account', 'staff_add'), meta: { title: '新增工作人员', resource: '/staff_account', requiresAuth: true, menuCode: 'staff_add', createMode: true } },
|
||||
@@ -97,10 +97,10 @@ const routes: AppRouteRecordRaw[] = [
|
||||
child('gasorder', 'track-points', 'track-points', '轨迹点', '/gasorder_track_point', 'gasorder_track', true, 'gasorder-tracks'),
|
||||
child('gasorder', 'confirms', 'confirms', '确认记录', '/gasorder_confirm', 'gasorder_basic', true, 'gasorder-orders'),
|
||||
child('gasorder', 'payments', 'payments', '订单支付记录', '/gasorder_payment', 'gasorder_basic', true, 'gasorder-orders'),
|
||||
], 'delivery'),
|
||||
group('ec', 'ec', '商城管理', 'icon-gift', 70, [
|
||||
], 'gasorder'),
|
||||
group('ec', 'ec', '电商平台管理', 'icon-gift', 70, [
|
||||
child('ec', 'categories', 'categories', '商品分类', '/ec_category', 'ec_category'),
|
||||
child('ec', 'products', 'products', '商品', '/ec_product', 'ec_product'),
|
||||
child('ec', 'products', 'products', '商品管理', '/ec_product', 'ec_product'),
|
||||
child('ec', 'attributes', 'attributes', '商品属性', '/ec_product_attribute', 'ec_product', true, 'ec-products'),
|
||||
child('ec', 'images', 'images', '商品图片', '/ec_product_image', 'ec_product', true, 'ec-products'),
|
||||
child('ec', 'carts', 'carts', '购物车', '/ec_cart', 'ec_cart'),
|
||||
|
||||
@@ -68,7 +68,7 @@ const cards: { key: CountKey; label: string; hint: string; money?: boolean }[] =
|
||||
{ key: 'paid_amount', label: '累计实收金额', hint: '支付成功口径', money: true },
|
||||
];
|
||||
const actions = [
|
||||
{ label: '新建气站', route: 'gas-basic', menu: 'gas_basic', icon: IconPlus },
|
||||
{ label: '新建气站', route: 'organization-gas-basic', menu: 'gas_basic', icon: IconPlus },
|
||||
{ label: '配送订单', route: 'gasorder-orders', menu: 'gasorder_basic', icon: IconFile },
|
||||
{ label: '智能气阀', route: 'product-info', menu: 'product_info', icon: IconStorage },
|
||||
{ label: '用户管理', route: 'user-account', menu: 'user_account', icon: IconUser },
|
||||
|
||||
@@ -14,19 +14,6 @@
|
||||
<a-button type="primary" html-type="submit">查询</a-button>
|
||||
<a-button @click="resetSearch">重置</a-button>
|
||||
</a-form>
|
||||
<a-form v-if="definition.name === 'wallet_basic'" :model="walletOwner" layout="inline" class="wallet-owner" @submit.prevent="getOwnerWallet">
|
||||
<a-form-item label="归属类型">
|
||||
<a-select v-model="walletOwner.type" style="width: 140px">
|
||||
<a-option value="user">用户</a-option>
|
||||
<a-option value="staff">工作人员</a-option>
|
||||
<a-option value="delivery">配送站</a-option>
|
||||
<a-option value="gas">气站</a-option>
|
||||
<a-option value="platform">平台</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="归属标识"><a-input v-model="walletOwner.identity" placeholder="请输入 identity" /></a-form-item>
|
||||
<a-button type="primary" html-type="submit">获取或创建钱包</a-button>
|
||||
</a-form>
|
||||
<a-table :data="list" :loading="loading" :pagination="false" row-key="identity">
|
||||
<template #columns>
|
||||
<a-table-column title="业务标识" data-index="identity" :width="220" ellipsis tooltip />
|
||||
@@ -114,7 +101,7 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-space class="detail-actions">
|
||||
<a-button v-for="action in definition.detailActions" :key="action.name" type="primary" :status="action.danger ? 'danger' : 'normal'" @click="openDetailAction(action)">{{ action.name }}</a-button>
|
||||
<a-button v-for="action in visibleDetailActions" :key="action.name" type="primary" :status="action.danger ? 'danger' : 'normal'" @click="openDetailAction(action)">{{ action.name }}</a-button>
|
||||
</a-space>
|
||||
</a-drawer>
|
||||
|
||||
@@ -173,7 +160,6 @@ const walletByOwner = ref<Record<string, Row>>({});
|
||||
const filters = reactive({
|
||||
keyword: typeof route.query.keyword === 'string' ? route.query.keyword : '',
|
||||
});
|
||||
const walletOwner = reactive({ type: 'user', identity: '' });
|
||||
const formVisible = ref(false);
|
||||
const detailVisible = ref(false);
|
||||
const editingIdentity = ref('');
|
||||
@@ -289,6 +275,14 @@ const currentIdentity = computed(() =>
|
||||
'',
|
||||
),
|
||||
);
|
||||
const visibleDetailActions = computed(() =>
|
||||
(props.definition.detailActions ?? []).filter((action) => {
|
||||
if (!action.visibleFor) return true;
|
||||
return action.visibleFor.values.includes(
|
||||
detail.value[action.visibleFor.field] as string | number,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
function resetForm(data?: Row) {
|
||||
for (const field of props.definition.fields) {
|
||||
@@ -306,31 +300,36 @@ async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (staffType.value) {
|
||||
const staff = await loadAllRows(props.definition.resource);
|
||||
const keyword = filters.keyword.trim().toLowerCase();
|
||||
const filtered = staff.filter(
|
||||
(row) =>
|
||||
String(row.role_code ?? '') === staffType.value &&
|
||||
(!keyword ||
|
||||
['username', 'name', 'phone'].some((key) =>
|
||||
String(row[key] ?? '').toLowerCase().includes(keyword),
|
||||
)),
|
||||
const result = await resourceApi.list<Row>(
|
||||
props.definition.resource,
|
||||
page.value,
|
||||
pageSize,
|
||||
{
|
||||
role_code: staffType.value,
|
||||
...(filters.keyword ? { keyword: filters.keyword } : {}),
|
||||
},
|
||||
);
|
||||
list.value = filtered.slice((page.value - 1) * pageSize, page.value * pageSize);
|
||||
total.value = filtered.length;
|
||||
list.value = result.list;
|
||||
total.value = result.total;
|
||||
} else if (managedOwnerIdentity.value && managedRelationKey.value) {
|
||||
const accounts = await loadAllRows(props.definition.resource);
|
||||
const keyword = filters.keyword.trim().toLowerCase();
|
||||
const filtered = accounts.filter(
|
||||
(row) =>
|
||||
String(row[managedRelationKey.value] ?? '') === managedOwnerIdentity.value &&
|
||||
(!keyword ||
|
||||
['username', 'display_name', 'role_code'].some((key) =>
|
||||
String(row[key] ?? '').toLowerCase().includes(keyword),
|
||||
)),
|
||||
const serverFilters: Record<string, string> = props.definition.name === 'staff_credential'
|
||||
? { staff_account_identity: managedOwnerIdentity.value }
|
||||
: props.definition.name === 'gas_account'
|
||||
? { gas_basic_identities: managedOwnerIdentity.value }
|
||||
: props.definition.name === 'delivery_account'
|
||||
? { delivery_basic_identities: managedOwnerIdentity.value }
|
||||
: {};
|
||||
const result = await resourceApi.list<Row>(
|
||||
props.definition.resource,
|
||||
page.value,
|
||||
pageSize,
|
||||
{
|
||||
...serverFilters,
|
||||
...(filters.keyword ? { keyword: filters.keyword } : {}),
|
||||
},
|
||||
);
|
||||
list.value = filtered.slice((page.value - 1) * pageSize, page.value * pageSize);
|
||||
total.value = filtered.length;
|
||||
list.value = result.list;
|
||||
total.value = result.total;
|
||||
} else {
|
||||
const result = await resourceApi.list<Row>(
|
||||
props.definition.resource,
|
||||
@@ -356,7 +355,17 @@ async function loadWallets() {
|
||||
walletByOwner.value = {};
|
||||
return;
|
||||
}
|
||||
const wallets = await loadAllRows('/wallet_basic');
|
||||
const ownerIdentities = list.value.map((row) => String(row.identity ?? '')).filter(Boolean);
|
||||
if (!ownerIdentities.length) {
|
||||
walletByOwner.value = {};
|
||||
return;
|
||||
}
|
||||
const wallets = (
|
||||
await resourceApi.list<Row>('/wallet_basic', 1, 100, {
|
||||
owner_type: ownerType,
|
||||
owner_identities: ownerIdentities.join(','),
|
||||
})
|
||||
).list;
|
||||
walletByOwner.value = wallets.reduce<Record<string, Row>>((result, wallet) => {
|
||||
if (wallet.owner_type === ownerType) {
|
||||
result[String(wallet.owner_identity ?? '')] = wallet;
|
||||
@@ -365,10 +374,10 @@ async function loadWallets() {
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function loadAllRows(resource: string) {
|
||||
async function loadAllRows(resource: string, filters: Record<string, string> = {}) {
|
||||
const rows: Row[] = [];
|
||||
for (let currentPage = 1; currentPage <= 100; currentPage += 1) {
|
||||
const result = await resourceApi.list<Row>(resource, currentPage, 100);
|
||||
const result = await resourceApi.list<Row>(resource, currentPage, 100, filters);
|
||||
rows.push(...result.list);
|
||||
if (rows.length >= result.total || result.list.length < 100) break;
|
||||
}
|
||||
@@ -381,7 +390,17 @@ async function loadAccountCounts() {
|
||||
accountCounts.value = {};
|
||||
return;
|
||||
}
|
||||
const accounts = await loadAllRows(management.resource);
|
||||
const ownerIdentities = list.value.map((row) => String(row.identity ?? '')).filter(Boolean);
|
||||
if (!ownerIdentities.length) {
|
||||
accountCounts.value = {};
|
||||
return;
|
||||
}
|
||||
const filterKey = management.relationKey === 'gas_basic_identity'
|
||||
? 'gas_basic_identities'
|
||||
: 'delivery_basic_identities';
|
||||
const accounts = await loadAllRows(management.resource, {
|
||||
[filterKey]: ownerIdentities.join(','),
|
||||
});
|
||||
accountCounts.value = accounts.reduce<Record<string, number>>((counts, account) => {
|
||||
const identity = String(account[management.relationKey] ?? '');
|
||||
if (identity) counts[identity] = (counts[identity] ?? 0) + 1;
|
||||
@@ -619,23 +638,6 @@ async function changePage(next: number) {
|
||||
await load();
|
||||
}
|
||||
|
||||
async function getOwnerWallet() {
|
||||
if (!walletOwner.identity.trim()) {
|
||||
Message.warning('请输入归属标识');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
detail.value = await resourceApi.detail<Row>(
|
||||
'/wallet_basic/owner',
|
||||
`${walletOwner.type}/${walletOwner.identity.trim()}`,
|
||||
);
|
||||
detailVisible.value = true;
|
||||
await load();
|
||||
} catch (error) {
|
||||
Message.error((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
function formatValue(value: unknown) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
@@ -718,12 +720,14 @@ function displayValue(key: string, value: unknown) {
|
||||
async function loadRelation(resource: string, keyword = '') {
|
||||
relationLoading[resource] = true;
|
||||
try {
|
||||
const extraFilters: Record<string, string> = keyword ? { keyword } : {};
|
||||
if (resource === '/staff_account') extraFilters.role_code = 'delivery';
|
||||
relationOptions[resource] = (
|
||||
await resourceApi.list<Row>(
|
||||
resource,
|
||||
1,
|
||||
100,
|
||||
keyword ? { keyword } : {},
|
||||
extraFilters,
|
||||
)
|
||||
).list;
|
||||
} catch (error) {
|
||||
@@ -745,7 +749,9 @@ function searchRelation(resource: string | undefined, keyword: string) {
|
||||
|
||||
onMounted(async () => {
|
||||
if (!route.meta.createMode) await load();
|
||||
const actionFields = props.definition.detailActions?.flatMap((item) => item.fields ?? []) ?? [];
|
||||
const actionFields = route.meta.createMode
|
||||
? []
|
||||
: props.definition.detailActions?.flatMap((item) => item.fields ?? []) ?? [];
|
||||
const relationPaths = new Set(
|
||||
[...props.definition.fields, ...actionFields]
|
||||
.map((field) => field.relation)
|
||||
@@ -789,11 +795,6 @@ function optionLabel(option: Row) {
|
||||
.detail-actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.wallet-owner {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
.muted-text {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user