|
|
@@ -0,0 +1,307 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import type { DataTableColumns, FormInst, FormRules } from 'naive-ui'
|
|
|
+import {
|
|
|
+ NButton,
|
|
|
+ NDataTable,
|
|
|
+ NForm,
|
|
|
+ NFormItem,
|
|
|
+ NFormItemGi,
|
|
|
+ NInput,
|
|
|
+ NInputNumber,
|
|
|
+ NModal,
|
|
|
+ NPagination,
|
|
|
+ NSpace,
|
|
|
+ useDialog,
|
|
|
+ useMessage,
|
|
|
+} from 'naive-ui'
|
|
|
+import AdminSearchPanel from '@/components/AdminSearchPanel.vue'
|
|
|
+import AdminTablePageBar from '@/components/AdminTablePageBar.vue'
|
|
|
+import { useConfirmRowDelete, useTableColumnsControl } from '@/composables'
|
|
|
+import type { RewardAmountItem, RewardAmountSearchParams } from '@/api/modules/settings/rewardAmount'
|
|
|
+import * as rewardAmountApi from '@/api/modules/settings/rewardAmount'
|
|
|
+
|
|
|
+const message = useMessage()
|
|
|
+const dialog = useDialog()
|
|
|
+
|
|
|
+const loading = ref(false)
|
|
|
+const list = ref<RewardAmountItem[]>([])
|
|
|
+const total = ref(0)
|
|
|
+const page = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+
|
|
|
+const search = ref<{ email: string; amount: string }>({
|
|
|
+ email: '',
|
|
|
+ amount: '',
|
|
|
+})
|
|
|
+
|
|
|
+function amountText(v: unknown) {
|
|
|
+ const n = Number(v ?? 0)
|
|
|
+ if (Number.isNaN(n)) return '0.00'
|
|
|
+ return n.toLocaleString(undefined, {
|
|
|
+ minimumFractionDigits: 2,
|
|
|
+ maximumFractionDigits: 2,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function buildSearchPayload(): RewardAmountSearchParams {
|
|
|
+ const amountRaw = search.value.amount.trim()
|
|
|
+ let amount: number | string | undefined
|
|
|
+ if (amountRaw) {
|
|
|
+ const n = Number(amountRaw)
|
|
|
+ amount = Number.isNaN(n) ? amountRaw : n
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ email: search.value.email.trim() || undefined,
|
|
|
+ amount,
|
|
|
+ page: { current: page.value, row: pageSize.value },
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchList() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const { list: rows, total: count } = await rewardAmountApi.searchRewardAmountList(
|
|
|
+ buildSearchPayload(),
|
|
|
+ )
|
|
|
+ list.value = rows
|
|
|
+ total.value = count
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onSearch() {
|
|
|
+ page.value = 1
|
|
|
+ void fetchList()
|
|
|
+}
|
|
|
+
|
|
|
+function resetSearch() {
|
|
|
+ search.value = { email: '', amount: '' }
|
|
|
+ page.value = 1
|
|
|
+ void fetchList()
|
|
|
+}
|
|
|
+
|
|
|
+const { confirmDelete } = useConfirmRowDelete<RewardAmountItem>({
|
|
|
+ dialog,
|
|
|
+ message,
|
|
|
+ title: '删除赠金额度',
|
|
|
+ content: '确定删除该条赠金额度吗?',
|
|
|
+ deleteRow: (row) => rewardAmountApi.deleteRewardAmount({ ids: [row.id] }),
|
|
|
+ onAfterDelete: () => fetchList(),
|
|
|
+})
|
|
|
+
|
|
|
+const showModal = ref(false)
|
|
|
+const submitting = ref(false)
|
|
|
+const editingRow = ref<RewardAmountItem | null>(null)
|
|
|
+const formRef = ref<FormInst | null>(null)
|
|
|
+const form = ref<{ email: string; amount: number | null }>({
|
|
|
+ email: '',
|
|
|
+ amount: null,
|
|
|
+})
|
|
|
+
|
|
|
+const rules: FormRules = {
|
|
|
+ email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
|
|
|
+ amount: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ type: 'number',
|
|
|
+ message: '请输入金额',
|
|
|
+ trigger: ['blur', 'change'],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+}
|
|
|
+
|
|
|
+function openAdd() {
|
|
|
+ editingRow.value = null
|
|
|
+ form.value = { email: '', amount: null }
|
|
|
+ showModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function openEdit(row: RewardAmountItem) {
|
|
|
+ editingRow.value = row
|
|
|
+ form.value = {
|
|
|
+ email: row.email ?? '',
|
|
|
+ amount: Number(row.amount ?? 0),
|
|
|
+ }
|
|
|
+ showModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+async function submit() {
|
|
|
+ await formRef.value?.validate()
|
|
|
+ if (form.value.amount == null) {
|
|
|
+ message.warning('请输入金额')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ submitting.value = true
|
|
|
+ try {
|
|
|
+ const payload = {
|
|
|
+ email: form.value.email.trim(),
|
|
|
+ amount: form.value.amount,
|
|
|
+ }
|
|
|
+ if (editingRow.value) {
|
|
|
+ await rewardAmountApi.updateRewardAmount(payload)
|
|
|
+ message.success('已更新')
|
|
|
+ } else {
|
|
|
+ await rewardAmountApi.addRewardAmount(payload)
|
|
|
+ message.success('已添加')
|
|
|
+ }
|
|
|
+ showModal.value = false
|
|
|
+ await fetchList()
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const allColumns = ref<DataTableColumns<RewardAmountItem>>([
|
|
|
+ {
|
|
|
+ title: '邮箱',
|
|
|
+ key: 'email',
|
|
|
+ ellipsis: { tooltip: true },
|
|
|
+ // minWidth: 220,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '金额',
|
|
|
+ key: 'amount',
|
|
|
+ // width: 140,
|
|
|
+ render: (row) => amountText(row.amount),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'actions',
|
|
|
+ width: 140,
|
|
|
+ fixed: 'right',
|
|
|
+ render(row) {
|
|
|
+ return h('div', { style: 'display:flex;gap:8px' }, [
|
|
|
+ h(
|
|
|
+ NButton,
|
|
|
+ {
|
|
|
+ size: 'small',
|
|
|
+ quaternary: true,
|
|
|
+ type: 'info',
|
|
|
+ onClick: () => openEdit(row),
|
|
|
+ },
|
|
|
+ { default: () => '编辑' },
|
|
|
+ ),
|
|
|
+ h(
|
|
|
+ NButton,
|
|
|
+ {
|
|
|
+ size: 'small',
|
|
|
+ quaternary: true,
|
|
|
+ type: 'error',
|
|
|
+ onClick: () => confirmDelete(row),
|
|
|
+ },
|
|
|
+ { default: () => '删除' },
|
|
|
+ ),
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ },
|
|
|
+])
|
|
|
+
|
|
|
+const { visibleKeys, displayColumns, columnOptions } = useTableColumnsControl(allColumns)
|
|
|
+
|
|
|
+watch([page, pageSize], () => {
|
|
|
+ void fetchList()
|
|
|
+})
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ void fetchList()
|
|
|
+})
|
|
|
+
|
|
|
+onActivated(() => {
|
|
|
+ void fetchList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="page page--table">
|
|
|
+ <AdminSearchPanel :field-count="2" @search="onSearch" @reset="resetSearch">
|
|
|
+ <NFormItemGi :span="1" label="邮箱">
|
|
|
+ <NInput
|
|
|
+ v-model:value="search.email"
|
|
|
+ clearable
|
|
|
+ placeholder="邮箱"
|
|
|
+ @keyup.enter="onSearch"
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi :span="1" label="金额">
|
|
|
+ <NInput
|
|
|
+ v-model:value="search.amount"
|
|
|
+ clearable
|
|
|
+ placeholder="金额"
|
|
|
+ @keyup.enter="onSearch"
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </AdminSearchPanel>
|
|
|
+
|
|
|
+ <NCard :bordered="false" class="table-card table-card--fill">
|
|
|
+ <div class="table-card-inner">
|
|
|
+ <AdminTablePageBar
|
|
|
+ title="赠金额度设置"
|
|
|
+ v-model:visible-keys="visibleKeys"
|
|
|
+ :column-options="columnOptions"
|
|
|
+ :refresh-loading="loading"
|
|
|
+ @refresh="fetchList"
|
|
|
+ >
|
|
|
+ <NButton @click="openAdd">新建</NButton>
|
|
|
+ </AdminTablePageBar>
|
|
|
+
|
|
|
+ <div class="table-card__body">
|
|
|
+ <NDataTable
|
|
|
+ :columns="displayColumns"
|
|
|
+ :data="list"
|
|
|
+ :loading="loading"
|
|
|
+ :bordered="false"
|
|
|
+ :single-line="false"
|
|
|
+ flex-height
|
|
|
+ class="data-table-fill"
|
|
|
+ :row-key="(row: RewardAmountItem) => String(row.id)"
|
|
|
+ :scroll-x="520"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="pager-wrap">
|
|
|
+ <div class="pager-inline">
|
|
|
+ <span class="pager-total">共 {{ total }} 条</span>
|
|
|
+ <NPagination
|
|
|
+ v-model:page="page"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :item-count="total"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ show-size-picker
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </NCard>
|
|
|
+
|
|
|
+ <NModal
|
|
|
+ v-model:show="showModal"
|
|
|
+ preset="card"
|
|
|
+ :title="editingRow ? '编辑赠金额度' : '新建赠金额度'"
|
|
|
+ style="width: min(480px, 92vw)"
|
|
|
+ :mask-closable="false"
|
|
|
+ @after-leave="() => { formRef?.restoreValidation(); editingRow = null }"
|
|
|
+ >
|
|
|
+ <NForm ref="formRef" :model="form" :rules="rules" label-placement="top">
|
|
|
+ <NFormItem label="邮箱" path="email">
|
|
|
+ <NInput v-model:value="form.email" placeholder="请输入邮箱" />
|
|
|
+ </NFormItem>
|
|
|
+ <NFormItem label="金额" path="amount">
|
|
|
+ <NInputNumber
|
|
|
+ v-model:value="form.amount"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ placeholder="请输入金额"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </NFormItem>
|
|
|
+ </NForm>
|
|
|
+ <template #footer>
|
|
|
+ <NSpace justify="end">
|
|
|
+ <NButton @click="showModal = false">取消</NButton>
|
|
|
+ <NButton type="primary" :loading="submitting" @click="submit">保存</NButton>
|
|
|
+ </NSpace>
|
|
|
+ </template>
|
|
|
+ </NModal>
|
|
|
+ </div>
|
|
|
+</template>
|