diff --git a/message_render_content.txt b/message_render_content.txt new file mode 100644 index 0000000..58cbbac Binary files /dev/null and b/message_render_content.txt differ diff --git a/src/components/SongList.vue b/src/components/SongList.vue index 002642c..268df0f 100644 --- a/src/components/SongList.vue +++ b/src/components/SongList.vue @@ -87,6 +87,33 @@ const batchUpdate_Option = ref() // 批量编辑 const columns = ref>() // 表格列定义 const selectedColumn = ref([]) // 表格选中行的 Key 数组 +// 分页相关 +const currentPage = ref(1) // 当前页码 +const handlePageChange = (page: number) => { + currentPage.value = page +} + +// 暴露分页方法 +const nextPage = () => { + const pagination = songsComputed.value.length > 0 ? Math.ceil(songsComputed.value.length / pageSize.value) : 1 + if (currentPage.value < pagination) { + currentPage.value++ + } +} + +const prevPage = () => { + if (currentPage.value > 1) { + currentPage.value-- + } +} + +// 暴露给父组件 +defineExpose({ + nextPage, + prevPage, + currentPage +}) + // --- 计算属性 --- // 筛选后的歌曲列表 @@ -163,8 +190,6 @@ const authorsOptions = computed(() => { })) }) -// --- 表格列定义 --- - // 作者列定义 (包含筛选逻辑) const authorColumn = ref>({ title: '作者', @@ -751,7 +776,8 @@ onMounted(() => { pageSizes: [10, 25, 50, 100, 200], showSizePicker: true, showQuickJumper: true, - + page: currentPage, + onUpdatePage: handlePageChange }" :loading="isLoading && songsComputed.length === 0" striped diff --git a/src/components/manage/PointOrderCard.vue b/src/components/manage/PointOrderCard.vue index b69f017..5720e4f 100644 --- a/src/components/manage/PointOrderCard.vue +++ b/src/components/manage/PointOrderCard.vue @@ -32,58 +32,101 @@ import { NTooltip, useDialog, useMessage, + NCard, + NSpace, + NAlert, } from 'naive-ui' import { computed, h, onMounted, ref, watch } from 'vue' import AddressDisplay from './AddressDisplay.vue' import PointGoodsItem from './PointGoodsItem.vue' +type OrderType = ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel + const props = defineProps<{ order: ResponsePointOrder2UserModel[] | ResponsePointOrder2OwnerModel[] type: 'user' | 'owner' goods?: ResponsePointGoodModel[] loading?: boolean }>() + const message = useMessage() const dialog = useDialog() - -const isLoading = ref(false) -watch( - () => props.loading, - () => { - isLoading.value = props.loading - }, -) -const orderAsUser = computed(() => { - return props.order as ResponsePointOrder2UserModel[] -}) -const orderAsOwner = computed(() => { - return props.order as ResponsePointOrder2OwnerModel[] -}) -const selectedItem = ref() - const emit = defineEmits(['selectedItem']) +// 状态管理 +const isLoading = ref(false) const showDetailModal = ref(false) -const orderDetail = ref() -const currentGoods = computed(() => { - //@ts-ignore - if (props.type == 'user') return orderDetail.value.goods - //@ts-ignore - else return props.goods.find((g) => g.id == orderDetail.value.goodsId) +const selectedItem = ref([]) +const orderDetail = ref() + +// 监听加载状态 +watch(() => props.loading, (val) => { + isLoading.value = !!val }) + +// 计算属性 +const orderAsUser = computed(() => props.order as ResponsePointOrder2UserModel[]) +const orderAsOwner = computed(() => props.order as ResponsePointOrder2OwnerModel[]) + +const currentGoods = computed(() => { + if (!orderDetail.value) return null + + if (props.type === 'user') { + return (orderDetail.value as ResponsePointOrder2UserModel).goods + } else { + return props.goods?.find((g) => g.id === (orderDetail.value as ResponsePointOrder2OwnerModel).goodsId) + } +}) + const expressOptions = computed(() => { - if (!orderAsOwner.value) return [] - return orderAsOwner.value.map((o) => ({ - label: o.expressCompany, - value: o.expressCompany, + if (props.type !== 'owner' || !orderAsOwner.value) return [] + + // 过滤掉空值并去重 + const companies = [...new Set( + orderAsOwner.value + .map(o => o.expressCompany) + .filter(Boolean) + )] + + return companies.map(company => ({ + label: company, + value: company, })) }) -const orderColumn: DataTableColumns = [ +// 状态映射表 +const statusMap = { + [PointOrderStatus.Pending]: { + text: '等待发货', + type: 'default', + description: '订单创建完成,等待主播发货', + action: '发货', + nextStatusText: '确认发货后,订单状态将变为"已发货"', + prevStatusText: '' + }, + [PointOrderStatus.Shipped]: { + text: (hasExpress: boolean) => hasExpress ? '已发货 | 已填写单号' : '已发货 | 未填写单号', + type: (hasExpress: boolean) => hasExpress ? 'info' : 'warning', + description: '订单已发货,可以添加快递信息', + action: '完成订单', + nextStatusText: '确认后将可以进行发货信息填写', + prevStatusText: '回退到"等待发货"状态,适用于发货信息填写错误等情况' + }, + [PointOrderStatus.Completed]: { + text: '已完成', + type: 'success', + description: '订单已完成', + action: '', + nextStatusText: '完成后无法再进行状态修改', + prevStatusText: '回退到"已发货"状态(仅限实体礼物)' + }, +} + +// 表格列定义 +const orderColumn: DataTableColumns = [ { type: 'selection', - - disabled: () => props.type == 'user', + disabled: () => props.type === 'user', options: [ 'all', 'none', @@ -91,8 +134,9 @@ const orderColumn: DataTableColumns { - selectedItem.value = pageData.filter((row) => row.status == PointOrderStatus.Pending).map((row) => row.id) - console.log(selectedItem.value) + selectedItem.value = pageData + .filter((row) => row.status === PointOrderStatus.Pending) + .map((row) => row.id) }, }, ], @@ -104,32 +148,36 @@ const orderColumn: DataTableColumns props.type == 'user', - render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { - return row.instanceOf == 'user' - ? '' - : h(NTooltip, null, { - trigger: () => - h( - NButton, - { - text: true, - type: 'primary', - tag: 'a', - href: 'https://space.bilibili.com/' + row.customer?.userId + '', - target: '_blank', - }, - { default: () => row.customer?.name || '未知用户' }, - ), - default: () => row.customer?.userId || '未知ID', - }) + disabled: () => props.type === 'user', + render: (row: OrderType) => { + if (row.instanceOf === 'user') return '' + + const ownerRow = row as ResponsePointOrder2OwnerModel + return h(NTooltip, null, { + trigger: () => + h( + NButton, + { + text: true, + type: 'primary', + tag: 'a', + href: `https://space.bilibili.com/${ownerRow.customer?.userId || ''}`, + target: '_blank', + }, + { default: () => ownerRow.customer?.name || '未知用户' }, + ), + default: () => ownerRow.customer?.userId || '未知ID', + }) }, }, { title: '礼物名', key: 'giftName', - render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { - return row.instanceOf == 'user' ? row.goods.name : props.goods?.find((g) => g.id == row.goodsId)?.name + render: (row: OrderType) => { + if (row.instanceOf === 'user') { + return (row as ResponsePointOrder2UserModel).goods.name + } + return props.goods?.find((g) => g.id === row.goodsId)?.name || '未知礼物' }, }, { @@ -141,7 +189,7 @@ const orderColumn: DataTableColumns { + render: (row: OrderType) => { return h(NTooltip, null, { trigger: () => h(NTime, { time: row.createAt, type: 'relative' }), default: () => h(NTime, { time: row.createAt }), @@ -156,88 +204,64 @@ const orderColumn: DataTableColumns { - return row.status == filterOptionValue - }, + filter: props.type === 'owner' + ? undefined + : (filterOptionValue: unknown, row: OrderType) => row.status === filterOptionValue, filterOptions: [ - { - label: '等待发货', - value: PointOrderStatus.Pending, - }, - { - label: '已发货', - value: PointOrderStatus.Shipped, - }, - { - label: '已完成', - value: PointOrderStatus.Completed, - }, + { label: '等待发货', value: PointOrderStatus.Pending }, + { label: '已发货', value: PointOrderStatus.Shipped }, + { label: '已完成', value: PointOrderStatus.Completed }, ], - render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { - const statusMap = { - [PointOrderStatus.Pending]: { - text: '等待发货', - type: 'default' - }, - [PointOrderStatus.Shipped]: { - text: row.expressCompany ? '已发货 | 已填写单号' : '已发货 | 未填写单号', - type: row.expressCompany ? 'info' : 'warning' - }, - [PointOrderStatus.Completed]: { - text: '已完成', - type: 'success' - } - } - + render: (row: OrderType) => { const status = statusMap[row.status] || { text: '未知状态', type: 'error' } + const hasExpress = !!row.expressCompany + + const text = typeof status.text === 'function' ? status.text(hasExpress) : status.text + const type = typeof status.type === 'function' ? status.type(hasExpress) : status.type return h(NTag, { size: 'small', - type: status.type as any, + type: type as any, bordered: false - }, () => status.text) + }, () => text) }, }, { title: '订单类型', key: 'type', - filter: - props.type == 'owner' - ? undefined - : (filterOptionValue: unknown, row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { - return row.type == filterOptionValue - }, + filter: props.type === 'owner' + ? undefined + : (filterOptionValue: unknown, row: OrderType) => row.type === filterOptionValue, filterOptions: [ - { - label: '实体礼物', - value: GoodsTypes.Physical, - }, - { - label: '虚拟礼物', - value: GoodsTypes.Virtual, - }, + { label: '实体礼物', value: GoodsTypes.Physical }, + { label: '虚拟礼物', value: GoodsTypes.Virtual }, ], - render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { - return h(NTag, { type: 'success', bordered: false, size: 'small' }, () => - row.type == GoodsTypes.Physical ? '实体礼物' : '虚拟礼物', - ) + render: (row: OrderType) => { + return h(NTag, { + type: 'success', + bordered: false, + size: 'small' + }, () => row.type === GoodsTypes.Physical ? '实体礼物' : '虚拟礼物') }, }, { title: '地址', key: 'address', minWidth: 250, - render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { - const collectUrl = - row.instanceOf == 'user' ? row.goods.collectUrl : props.goods?.find((g) => g.id == row.goodsId)?.collectUrl - if (row.type == GoodsTypes.Physical) { - return collectUrl - ? h(NButton, { tag: 'a', href: collectUrl, target: '_blank', text: true, type: 'info' }, () => - h(NText, { italic: true }, () => '通过站外链接收集'), - ) + render: (row: OrderType) => { + const goodsCollectUrl = row.instanceOf === 'user' + ? (row as ResponsePointOrder2UserModel).goods.collectUrl + : props.goods?.find((g) => g.id === row.goodsId)?.collectUrl + + if (row.type === GoodsTypes.Physical) { + return goodsCollectUrl + ? h(NButton, { + tag: 'a', + href: goodsCollectUrl, + target: '_blank', + text: true, + type: 'info' + }, () => h(NText, { italic: true }, () => '通过站外链接收集')) : h(AddressDisplay, { address: row.address }) } else { return h(NText, { depth: 3, italic: true }, () => '无需发货') @@ -248,36 +272,23 @@ const orderColumn: DataTableColumns { - if (row.type == GoodsTypes.Physical) { - return row.trackingNumber - ? h( - NFlex, - { - depth: 3, - }, - () => [ - h( - NTag, - { - size: 'tiny', - bordered: false, - }, - () => row.expressCompany, - ), - h(NText, { depth: 3 }, () => row.trackingNumber), - ], - ) - : h(NText, { depth: 3, italic: true }, () => '尚未发货') - } else { - return h(NText, { depth: 3, italic: true }, () => '无需发货') + render: (row: OrderType) => { + if (row.type === GoodsTypes.Physical) { + if (row.trackingNumber) { + return h(NFlex, { align: 'center', gap: 8 }, () => [ + h(NTag, { size: 'tiny', bordered: false }, () => row.expressCompany), + h(NText, { depth: 3 }, () => row.trackingNumber), + ]) + } + return h(NText, { depth: 3, italic: true }, () => '尚未发货') } + return h(NText, { depth: 3, italic: true }, () => '无需发货') }, }, { title: '操作', key: 'action', - render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => { + render: (row: OrderType) => { return h( NButton, { @@ -293,10 +304,65 @@ const orderColumn: DataTableColumns (orderDetail.value?.status || 0)) { + tipText = statusInfo.nextStatusText + + // 特殊处理虚拟礼物 + if (orderDetail.value?.type === GoodsTypes.Virtual && status === PointOrderStatus.Completed) { + tipText = '该虚拟礼物将被标记为已完成' + } + } else if (status < (orderDetail.value?.status || 0)) { + tipText = statusInfo.prevStatusText + } + dialog.info({ - title: '提示', - content: '确认修改订单状态?', + title: '修改订单状态', + content: () => h('div', null, [ + h('p', null, `确认将订单状态从「${currentStatusText}」修改为「${newStatusText}」吗?`), + tipText ? h('p', { style: 'color: #f90; margin-top: 8px;' }, tipText) : null + ]), positiveText: '确认', negativeText: '取消', onPositiveClick: () => { @@ -304,17 +370,21 @@ function onChangeStatus(id: number, status: PointOrderStatus) { }, }) } -async function updateStatus(id: number[], status: PointOrderStatus) { + +async function updateStatus(ids: number[], status: PointOrderStatus) { + if (!ids.length) return + try { isLoading.value = true const data = await QueryPostAPI(POINT_API_URL + 'update-orders-status', { - ids: id, + ids, status, }) - if (data.code == 200) { + + if (data.code === 200) { message.success('操作成功') props.order?.forEach((row) => { - if (id.includes(row.id)) { + if (ids.includes(row.id)) { row.status = status } }) @@ -323,16 +393,18 @@ async function updateStatus(id: number[], status: PointOrderStatus) { } } catch (err) { message.error('操作失败: ' + err) - console.log(err) + console.error(err) } finally { isLoading.value = false } } + async function updateExpress(item: ResponsePointOrder2OwnerModel) { if (!item.trackingNumber || !item.expressCompany) { message.error('请填写快递单号和快递公司') return } + try { isLoading.value = true const data = await QueryPostAPI(POINT_API_URL + 'update-order-express', { @@ -340,18 +412,21 @@ async function updateExpress(item: ResponsePointOrder2OwnerModel) { trackingNumber: item.trackingNumber, expressCompany: item.expressCompany, }) - if (data.code == 200) { + + if (data.code === 200) { message.success('操作成功') } else { message.error('操作失败: ' + data.message) } } catch (err) { message.error('操作失败: ' + err) - console.log(err) + console.error(err) + } finally { + isLoading.value = false } - isLoading.value = false } +// 初始化 onMounted(() => { props.order?.forEach((row) => { row.instanceOf = props.type @@ -360,151 +435,521 @@ onMounted(() => {