Files
TakeoutSaaS.AdminUI/src/views/store/store-list/index.vue

327 lines
11 KiB
Vue

<template>
<div class="art-page-view">
<StoreSearch v-model="formFilters" @search="handleSearch" @reset="handleReset" />
<ElCard class="art-table-card" shadow="never">
<ArtTableHeader
v-model:columns="columns"
v-model:showSearchBar="showSearchBar"
:loading="loading"
@refresh="refreshData"
>
<template #actions>
<ElButton type="primary" @click="handleCreate">
{{ t('store.list.action.create') }}
</ElButton>
</template>
</ArtTableHeader>
<ArtTable
:data="data"
:columns="columns"
:loading="loading"
:pagination="pagination"
@pagination:current-change="handleCurrentChange"
@pagination:size-change="handleSizeChange"
>
<template #auditStatus="{ row }">
<ElTag :type="getAuditStatusType(row.auditStatus)">
{{ getAuditStatusText(row.auditStatus) }}
</ElTag>
</template>
<template #businessStatus="{ row }">
<ElTag :type="getBusinessStatusType(row.businessStatus)">
{{ getBusinessStatusText(row.businessStatus) }}
</ElTag>
</template>
<template #ownershipType="{ row }">
<span>{{ getOwnershipTypeText(row.ownershipType) }}</span>
</template>
<template #action="{ row }">
<div class="action-wrap">
<button class="art-action-btn" @click="handleDetail(row)">
<ArtSvgIcon icon="ri:information-line" class="art-action-icon" />
<span>{{ t('dictionary.common.detail') }}</span>
</button>
<button class="art-action-btn primary" @click="handleEdit(row)">
<ArtSvgIcon icon="ri:edit-2-line" class="art-action-icon" />
<span>{{ t('store.list.action.edit') }}</span>
</button>
<ElDropdown trigger="click" @command="(cmd) => handleCommand(cmd, row)">
<button class="art-action-btn info">
<ArtSvgIcon icon="ri:more-2-line" class="art-action-icon" />
<span>{{ t('user.action.more') }}</span>
</button>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
:disabled="row.businessStatus === StoreBusinessStatus.ForceClosed"
command="toggleStatus"
>
<div class="flex items-center gap-2">
<ArtSvgIcon icon="ri:shut-down-line" />
<span>{{ t('store.list.action.toggleStatus') }}</span>
</div>
</ElDropdownItem>
<ElDropdownItem
v-if="canSubmitAudit(row)"
:disabled="submittingId === row.id"
command="submitAudit"
>
<div class="flex items-center gap-2">
<ArtSvgIcon icon="ri:check-double-line" />
<span>{{ t('store.list.action.submitAudit') }}</span>
</div>
</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
</div>
</template>
</ArtTable>
</ElCard>
<StoreFormDialog v-model="formDialogVisible" :store="editingStore" @saved="refreshData" />
<BusinessStatusDialog v-model="statusDialogVisible" :store="statusStore" @saved="refreshData" />
<StoreDetailDrawer ref="detailDrawerRef" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import {
ElButton,
ElCard,
ElMessage,
ElTag,
ElDropdown,
ElDropdownMenu,
ElDropdownItem
} from 'element-plus'
import ArtTable from '@/components/core/tables/art-table/index.vue'
import ArtTableHeader from '@/components/core/tables/art-table-header/index.vue'
import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
import { useTable } from '@/hooks/core/useTable'
import { fetchStoreList, fetchSubmitStoreAudit } from '@/api/store'
import { StoreAuditStatus } from '@/enums/StoreAuditStatus'
import { StoreBusinessStatus } from '@/enums/StoreBusinessStatus'
import { StoreOwnershipType } from '@/enums/StoreOwnershipType'
import { formatDateTime } from '@/utils/billing'
import StoreFormDialog from './components/StoreFormDialog.vue'
import BusinessStatusDialog from './components/BusinessStatusDialog.vue'
import StoreDetailDrawer from './components/StoreDetailDrawer.vue'
import StoreSearch from './modules/store-search.vue'
import type { ColumnOption } from '@/types/component'
defineOptions({ name: 'StoreList' })
interface StoreListFilters {
keyword: string
merchantId: string
auditStatus?: StoreAuditStatus
businessStatus?: StoreBusinessStatus
ownershipType?: StoreOwnershipType
}
// 1. 基础状态
const { t } = useI18n()
const showSearchBar = ref(true)
const formDialogVisible = ref(false)
const statusDialogVisible = ref(false)
const editingStore = ref<Api.Store.StoreDto | null>(null)
const statusStore = ref<Api.Store.StoreDto | null>(null)
const submittingId = ref<string>()
const detailDrawerRef = ref<InstanceType<typeof StoreDetailDrawer>>()
// 2. 搜索筛选
const formFilters = ref<StoreListFilters>({
keyword: '',
merchantId: '',
auditStatus: undefined,
businessStatus: undefined,
ownershipType: undefined
})
// 3. 表格配置
const {
data,
loading,
pagination,
columns,
searchParams,
refreshData,
handleCurrentChange,
handleSizeChange
} = useTable<typeof fetchStoreList, Api.Store.StoreDto>({
core: {
apiFn: fetchStoreList,
apiParams: {
Page: 1,
PageSize: 20
},
paginationKey: {
current: 'Page',
size: 'PageSize'
},
columnsFactory: (): ColumnOption<Api.Store.StoreDto>[] => [
{ type: 'globalIndex', label: '#', width: 60, fixed: 'left' },
{ prop: 'name', label: t('store.list.table.name'), minWidth: 160 },
{ prop: 'code', label: t('store.list.table.code'), minWidth: 140 },
{
prop: 'ownershipType',
label: t('store.list.table.ownershipType'),
width: 140,
useSlot: true
},
{
prop: 'auditStatus',
label: t('store.list.table.auditStatus'),
width: 120,
useSlot: true
},
{
prop: 'businessStatus',
label: t('store.list.table.businessStatus'),
width: 120,
useSlot: true
},
{ prop: 'phone', label: t('store.list.table.phone'), minWidth: 140 },
{
prop: 'createdAt',
label: t('store.list.table.createdAt'),
width: 180,
formatter: (row) => formatDateTime(row.createdAt)
},
{ prop: 'action', label: t('common.action'), width: 280, fixed: 'right', useSlot: true }
]
}
})
// 5. 搜索与操作
const handleSearch = () => {
searchParams.keyword = formFilters.value.keyword.trim() || undefined
searchParams.merchantId = formFilters.value.merchantId.trim() || undefined
searchParams.auditStatus = formFilters.value.auditStatus
searchParams.businessStatus = formFilters.value.businessStatus
searchParams.ownershipType = formFilters.value.ownershipType
refreshData()
}
const handleReset = () => {
formFilters.value = {
keyword: '',
merchantId: '',
auditStatus: undefined,
businessStatus: undefined,
ownershipType: undefined
}
searchParams.keyword = undefined
searchParams.merchantId = undefined
searchParams.auditStatus = undefined
searchParams.businessStatus = undefined
searchParams.ownershipType = undefined
refreshData()
}
const handleCreate = () => {
editingStore.value = null
formDialogVisible.value = true
}
const handleEdit = (row: Api.Store.StoreDto) => {
editingStore.value = row
formDialogVisible.value = true
}
const handleToggleStatus = (row: Api.Store.StoreDto) => {
statusStore.value = row
statusDialogVisible.value = true
}
const handleDetail = (row: Api.Store.StoreDto) => {
detailDrawerRef.value?.open(row.id)
}
const handleCommand = (command: string | number | object, row: Api.Store.StoreDto) => {
if (command === 'toggleStatus') {
handleToggleStatus(row)
} else if (command === 'submitAudit') {
handleSubmitAudit(row)
}
}
const canSubmitAudit = (row: Api.Store.StoreDto) =>
row.ownershipType === StoreOwnershipType.DifferentEntity &&
[StoreAuditStatus.Draft, StoreAuditStatus.Rejected].includes(row.auditStatus)
const handleSubmitAudit = async (row: Api.Store.StoreDto) => {
submittingId.value = row.id
try {
await fetchSubmitStoreAudit(row.id)
ElMessage.success(t('store.message.submitAuditSuccess'))
} finally {
submittingId.value = undefined
}
refreshData()
}
// 6. 状态显示
const getAuditStatusType = (status: StoreAuditStatus) => {
switch (status) {
case StoreAuditStatus.Draft:
return 'info'
case StoreAuditStatus.Pending:
return 'warning'
case StoreAuditStatus.Activated:
return 'success'
case StoreAuditStatus.Rejected:
return 'danger'
default:
return 'info'
}
}
const getAuditStatusText = (status: StoreAuditStatus) => {
switch (status) {
case StoreAuditStatus.Draft:
return t('store.auditStatus.draft')
case StoreAuditStatus.Pending:
return t('store.auditStatus.pending')
case StoreAuditStatus.Activated:
return t('store.auditStatus.activated')
case StoreAuditStatus.Rejected:
return t('store.auditStatus.rejected')
default:
return t('store.auditStatus.unknown')
}
}
const getBusinessStatusType = (status: StoreBusinessStatus) => {
switch (status) {
case StoreBusinessStatus.Open:
return 'success'
case StoreBusinessStatus.Resting:
return 'warning'
case StoreBusinessStatus.ForceClosed:
return 'danger'
default:
return 'info'
}
}
const getBusinessStatusText = (status: StoreBusinessStatus) => {
switch (status) {
case StoreBusinessStatus.Open:
return t('store.businessStatus.open')
case StoreBusinessStatus.Resting:
return t('store.businessStatus.resting')
case StoreBusinessStatus.ForceClosed:
return t('store.businessStatus.forceClosed')
default:
return t('store.businessStatus.unknown')
}
}
const getOwnershipTypeText = (type?: StoreOwnershipType) => {
switch (type) {
case StoreOwnershipType.SameEntity:
return t('store.ownership.same')
case StoreOwnershipType.DifferentEntity:
return t('store.ownership.different')
default:
return '-'
}
}
</script>