fix platform authorization and workflow integrity

This commit is contained in:
david
2026-07-29 15:54:20 +08:00
parent a7d318a013
commit 969d7271f9
26 changed files with 566 additions and 161 deletions

View File

@@ -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 },

View File

@@ -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);
}