feat: inline edit resource store status

This commit is contained in:
zhx 2026-05-17 01:02:17 +08:00
parent 1b835ba52d
commit fa5663bdbf

View File

@ -1,6 +1,7 @@
<script lang="ts" setup>
import {
computed,
nextTick,
reactive,
ref,
} from 'vue';
@ -13,6 +14,7 @@ import {
Card,
Empty,
Input,
InputNumber,
message,
Pagination,
Select,
@ -20,11 +22,14 @@ import {
Switch,
Table,
Tag,
Tooltip,
} from 'antdv-next';
import {
addOrUpdatePropsStore,
offShelfPropsSource,
pagePropsSource,
updatePropsSource,
} from '#/api/legacy/props';
import InlineFilterField from '#/views/_shared/inline-filter-field.vue';
import InlineFilterToolbar from '#/views/_shared/inline-filter-toolbar.vue';
@ -64,6 +69,9 @@ const formOpen = ref(false);
const salesOpen = ref(false);
const overviewOpen = ref(false);
const activeRow = ref<null | Record<string, any>>(null);
const amountEditingId = ref<null | string>(null);
const amountSavingId = ref<null | string>(null);
const amountDraft = ref<null | number>(null);
const query = reactive({
cursor: 1,
@ -83,6 +91,7 @@ const columns = [
{ dataIndex: 'cover', key: 'cover', title: '资源', width: 120 },
{ dataIndex: 'del', key: 'del', title: '上/下架', width: 120 },
{ dataIndex: 'amount', key: 'amount', title: '底价', width: 100 },
{ dataIndex: 'storeShelfStatus', key: 'storeShelfStatus', title: '是否上架道具商店', width: 160 },
{ dataIndex: 'adminFree', key: 'adminFree', title: 'Admin Free', width: 120 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 180 },
];
@ -122,6 +131,108 @@ async function handleStatusChange(record: Record<string, any>, checked: boolean)
}
}
function getRecordId(record: Record<string, any>) {
return String(record.id || '');
}
function isAmountEditing(record: Record<string, any>) {
return amountEditingId.value === getRecordId(record);
}
function isAmountSaving(record: Record<string, any>) {
return amountSavingId.value === getRecordId(record);
}
function startAmountEdit(record: Record<string, any>) {
if (!canEdit.value) {
return;
}
amountEditingId.value = getRecordId(record);
amountDraft.value = Number(record.amount ?? 0);
void nextTick();
}
function cancelAmountEdit() {
amountEditingId.value = null;
amountDraft.value = null;
}
function buildPropsSourceUpdatePayload(record: Record<string, any>, amount: number) {
return {
adminFree: Boolean(record.adminFree),
amount,
code: record.code,
cover: record.cover,
expand: record.expand,
id: record.id,
name: record.name,
sourceUrl: record.sourceUrl,
sysOrigin: record.sysOrigin,
type: record.type,
};
}
async function handleAmountConfirm(record: Record<string, any>) {
if (!isAmountEditing(record) || isAmountSaving(record)) {
return;
}
const amount = Number(amountDraft.value);
if (!Number.isFinite(amount) || amount < 0) {
message.warning('请输入正确的底价');
return;
}
const previous = record.amount;
if (Number(previous ?? 0) === amount) {
cancelAmountEdit();
return;
}
amountSavingId.value = getRecordId(record);
record.amount = amount;
try {
await updatePropsSource(buildPropsSourceUpdatePayload(record, amount));
message.success('底价已更新');
cancelAmountEdit();
} catch (error) {
record.amount = previous;
throw error;
} finally {
amountSavingId.value = null;
}
}
function getStoreCommodity(record: Record<string, any>) {
return record.storeCommodity || null;
}
function buildStoreShelfPayload(record: Record<string, any>, checked: boolean) {
const commodity = getStoreCommodity(record);
return {
...commodity,
label: commodity.label || '',
propsType: commodity.propsType || record.type,
shelfStatus: checked,
sourceId: commodity.sourceId || record.id,
sysOrigin: commodity.sysOrigin || record.sysOrigin,
};
}
async function handleStoreShelfStatusChange(record: Record<string, any>, checked: boolean) {
const commodity = getStoreCommodity(record);
if (!commodity) {
message.warning('请先在道具商店配置该资源');
return;
}
const previous = commodity.shelfStatus;
commodity.shelfStatus = checked;
try {
await addOrUpdatePropsStore(buildStoreShelfPayload(record, checked));
message.success('道具商店状态已更新');
} catch (error) {
commodity.shelfStatus = previous;
throw error;
}
}
function openCreate() {
activeRow.value = null;
formOpen.value = true;
@ -203,7 +314,7 @@ loadData(true);
:loading="loading"
:pagination="false"
row-key="id"
:scroll="{ x: 1160 }"
:scroll="{ x: 1320 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'cover'">
@ -217,6 +328,42 @@ loadData(true);
@change="(checked: boolean) => handleStatusChange(record, checked)"
/>
</template>
<template v-else-if="column.key === 'amount'">
<InputNumber
v-if="isAmountEditing(record)"
v-model:value="amountDraft"
autofocus
:disabled="isAmountSaving(record)"
:min="0"
size="small"
style="width: 92px"
@blur="() => handleAmountConfirm(record)"
@keydown.enter.prevent="() => handleAmountConfirm(record)"
@keydown.esc="cancelAmountEdit"
/>
<span
v-else
:class="['amount-value', { 'amount-value--editable': canEdit }]"
@click="startAmountEdit(record)"
>
{{ record.amount ?? 0 }}
</span>
</template>
<template v-else-if="column.key === 'storeShelfStatus'">
<Tooltip
:title="getStoreCommodity(record) ? '' : '请先在道具商店配置该资源'"
>
<Switch
:checked="Boolean(getStoreCommodity(record)?.shelfStatus)"
:disabled="!getStoreCommodity(record)"
checked-children="上架"
un-checked-children="下架"
@change="
(checked: boolean) => handleStoreShelfStatusChange(record, checked)
"
/>
</Tooltip>
</template>
<template v-else-if="column.key === 'adminFree'">
<Tag :color="record.adminFree ? 'green' : 'default'">
{{ record.adminFree ? '是' : '否' }}
@ -286,4 +433,14 @@ loadData(true);
.toolbar {
margin-bottom: 16px;
}
.amount-value {
display: inline-block;
min-width: 48px;
}
.amount-value--editable {
color: #1677ff;
cursor: pointer;
}
</style>