|
|
@@ -0,0 +1,777 @@
|
|
|
+<template>
|
|
|
+ <div class="rounded-lg p-4 bg-[var(--p-content-background)]">
|
|
|
+ <DataTable
|
|
|
+ :value="tableData.content"
|
|
|
+ :paginator="true"
|
|
|
+ paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
|
|
|
+ currentPageReportTemplate="{totalRecords} 条记录 "
|
|
|
+ :rows="tableData.metadata.size"
|
|
|
+ :rowsPerPageOptions="[10, 20, 50, 100]"
|
|
|
+ :totalRecords="tableData.metadata.total"
|
|
|
+ @page="handlePageChange"
|
|
|
+ lazy
|
|
|
+ scrollable
|
|
|
+ class="income-table"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="flex flex-wrap items-center gap-2">
|
|
|
+ <InputText v-model="searchForm.id" placeholder="ID" size="small" class="w-32" @keyup.enter="handleSearch" />
|
|
|
+ <InputText
|
|
|
+ v-model="searchForm.agentName"
|
|
|
+ placeholder="代理"
|
|
|
+ size="small"
|
|
|
+ class="w-32"
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ />
|
|
|
+ <Dropdown
|
|
|
+ v-model="searchForm.incomeType"
|
|
|
+ :options="incomeTypeOptions"
|
|
|
+ optionLabel="label"
|
|
|
+ optionValue="value"
|
|
|
+ placeholder="收入类型"
|
|
|
+ size="small"
|
|
|
+ class="w-32"
|
|
|
+ :showClear="true"
|
|
|
+ />
|
|
|
+ <Dropdown
|
|
|
+ v-model="searchForm.orderType"
|
|
|
+ :options="orderTypeOptions"
|
|
|
+ optionLabel="label"
|
|
|
+ optionValue="value"
|
|
|
+ placeholder="订单类型"
|
|
|
+ size="small"
|
|
|
+ class="w-32"
|
|
|
+ :showClear="true"
|
|
|
+ />
|
|
|
+ <Dropdown
|
|
|
+ v-model="searchForm.payChannel"
|
|
|
+ :options="payChannelOptions"
|
|
|
+ optionLabel="label"
|
|
|
+ optionValue="value"
|
|
|
+ placeholder="支付渠道"
|
|
|
+ size="small"
|
|
|
+ class="w-32"
|
|
|
+ :showClear="true"
|
|
|
+ />
|
|
|
+ <DatePicker
|
|
|
+ v-model="searchForm.startDate"
|
|
|
+ placeholder="开始日期"
|
|
|
+ size="small"
|
|
|
+ class="w-40"
|
|
|
+ dateFormat="yy-mm-dd"
|
|
|
+ showIcon
|
|
|
+ />
|
|
|
+ <DatePicker
|
|
|
+ v-model="searchForm.endDate"
|
|
|
+ placeholder="结束日期"
|
|
|
+ size="small"
|
|
|
+ class="w-40"
|
|
|
+ dateFormat="yy-mm-dd"
|
|
|
+ showIcon
|
|
|
+ />
|
|
|
+ <Button icon="pi pi-search" @click="handleSearch" label="搜索" size="small" severity="secondary" />
|
|
|
+ <Button icon="pi pi-refresh" @click="handleRefresh" label="刷新" size="small" />
|
|
|
+ <div class="flex-1"></div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <Column field="id" header="ID" style="width: 80px" frozen>
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span
|
|
|
+ class="font-mono text-sm copyable-text"
|
|
|
+ :title="slotProps.data.id"
|
|
|
+ @click="copyToClipboard(slotProps.data.id)"
|
|
|
+ >
|
|
|
+ {{ slotProps.data.id }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="agentName" header="代理" style="min-width: 130px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span
|
|
|
+ class="font-medium agent-name-text copyable-text"
|
|
|
+ :title="slotProps.data.agentName"
|
|
|
+ @click="copyToClipboard(slotProps.data.agentName)"
|
|
|
+ >
|
|
|
+ {{ slotProps.data.agentName }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column
|
|
|
+ field="incomeAmount"
|
|
|
+ header="收入金额"
|
|
|
+ style="min-width: 100px"
|
|
|
+ :pt="{
|
|
|
+ columnHeaderContent: {
|
|
|
+ class: 'justify-center'
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="amount-text font-semibold">{{ formatAmount(slotProps.data.incomeAmount) }} </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="incomeType" header="收入类型" style="min-width: 100px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="income-type-text" :class="getIncomeTypeClass(slotProps.data.incomeType)">
|
|
|
+ {{ getIncomeTypeText(slotProps.data.incomeType) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="video" header="影片" style="min-width: 200px; max-width: 260px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span
|
|
|
+ class="video-text copyable-text"
|
|
|
+ :title="slotProps.data.video"
|
|
|
+ @click="copyToClipboard(slotProps.data.video)"
|
|
|
+ >
|
|
|
+ {{ slotProps.data.video }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column
|
|
|
+ field="price"
|
|
|
+ header="价格"
|
|
|
+ style="min-width: 100px"
|
|
|
+ :pt="{
|
|
|
+ columnHeaderContent: {
|
|
|
+ class: 'justify-center'
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="price-text">{{ formatAmount(slotProps.data.price) }} </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="orderType" header="订单类型" style="min-width: 120px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="order-type-text">
|
|
|
+ {{ getOrderTypeText(slotProps.data.orderType) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="tipOrderId" header="订单号" style="min-width: 120px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span
|
|
|
+ class="font-mono text-sm order-id-text copyable-text"
|
|
|
+ :title="slotProps.data.tipOrderId"
|
|
|
+ @click="copyToClipboard(slotProps.data.tipOrderId)"
|
|
|
+ >
|
|
|
+ {{ slotProps.data.tipOrderId }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="payChannel" header="支付渠道" style="min-width: 100px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="pay-channel-text">
|
|
|
+ {{ getPayChannelText(slotProps.data.payChannel) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="payNo" header="支付单号" style="min-width: 120px; max-width: 180px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span
|
|
|
+ class="font-mono text-sm pay-no-text copyable-text"
|
|
|
+ :title="slotProps.data.payNo"
|
|
|
+ @click="copyToClipboard(slotProps.data.payNo)"
|
|
|
+ >
|
|
|
+ {{ slotProps.data.payNo }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="source" header="来源" style="min-width: 100px; max-width: 160px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="source-text">
|
|
|
+ {{ slotProps.data.source }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column field="createdAt" header="创建时间" style="min-width: 150px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="text-sm">
|
|
|
+ {{ formatDateTime(slotProps.data.createdAt) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+
|
|
|
+ <Column
|
|
|
+ header="操作"
|
|
|
+ style="min-width: 150px; width: 150px"
|
|
|
+ align-frozen="right"
|
|
|
+ frozen
|
|
|
+ :pt="{
|
|
|
+ columnHeaderContent: {
|
|
|
+ class: 'justify-center'
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #body="slotProps">
|
|
|
+ <div class="flex justify-center gap-1">
|
|
|
+ <Button
|
|
|
+ icon="pi pi-pencil"
|
|
|
+ severity="info"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ rounded
|
|
|
+ aria-label="编辑"
|
|
|
+ @click="openEditDialog(slotProps.data)"
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ icon="pi pi-trash"
|
|
|
+ severity="danger"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ rounded
|
|
|
+ aria-label="删除"
|
|
|
+ @click="confirmDelete(slotProps.data)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ </DataTable>
|
|
|
+
|
|
|
+ <!-- 编辑弹窗 -->
|
|
|
+ <Dialog
|
|
|
+ v-model:visible="editDialog"
|
|
|
+ :modal="true"
|
|
|
+ header="编辑收入记录"
|
|
|
+ :style="{ width: '600px' }"
|
|
|
+ position="center"
|
|
|
+ >
|
|
|
+ <div class="p-fluid">
|
|
|
+ <div class="grid grid-cols-2 gap-4">
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-agentName" class="font-medium text-sm mb-2 block">代理</label>
|
|
|
+ <InputText id="edit-agentName" v-model="editForm.agentName" class="w-full" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-incomeAmount" class="font-medium text-sm mb-2 block">收入金额</label>
|
|
|
+ <InputNumber
|
|
|
+ id="edit-incomeAmount"
|
|
|
+ v-model="editForm.incomeAmount"
|
|
|
+ mode="decimal"
|
|
|
+ :min-fraction-digits="2"
|
|
|
+ :max-fraction-digits="2"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4 mt-4">
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-incomeType" class="font-medium text-sm mb-2 block">收入类型</label>
|
|
|
+ <Dropdown
|
|
|
+ id="edit-incomeType"
|
|
|
+ v-model="editForm.incomeType"
|
|
|
+ :options="incomeTypeOptions.filter((option) => option.value !== null)"
|
|
|
+ optionLabel="label"
|
|
|
+ optionValue="value"
|
|
|
+ placeholder="选择收入类型"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-video" class="font-medium text-sm mb-2 block">影片</label>
|
|
|
+ <InputText id="edit-video" v-model="editForm.video" class="w-full" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4 mt-4">
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-price" class="font-medium text-sm mb-2 block">价格</label>
|
|
|
+ <InputNumber
|
|
|
+ id="edit-price"
|
|
|
+ v-model="editForm.price"
|
|
|
+ mode="decimal"
|
|
|
+ :min-fraction-digits="2"
|
|
|
+ :max-fraction-digits="2"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-orderType" class="font-medium text-sm mb-2 block">订单类型</label>
|
|
|
+ <Dropdown
|
|
|
+ id="edit-orderType"
|
|
|
+ v-model="editForm.orderType"
|
|
|
+ :options="orderTypeOptions.filter((option) => option.value !== null)"
|
|
|
+ optionLabel="label"
|
|
|
+ optionValue="value"
|
|
|
+ placeholder="选择订单类型"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4 mt-4">
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-tipOrderId" class="font-medium text-sm mb-2 block">订单号</label>
|
|
|
+ <InputText id="edit-tipOrderId" v-model="editForm.tipOrderId" class="w-full" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-payChannel" class="font-medium text-sm mb-2 block">支付渠道</label>
|
|
|
+ <Dropdown
|
|
|
+ id="edit-payChannel"
|
|
|
+ v-model="editForm.payChannel"
|
|
|
+ :options="payChannelOptions.filter((option) => option.value !== null)"
|
|
|
+ optionLabel="label"
|
|
|
+ optionValue="value"
|
|
|
+ placeholder="选择支付渠道"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4 mt-4">
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-payNo" class="font-medium text-sm mb-2 block">支付单号</label>
|
|
|
+ <InputText id="edit-payNo" v-model="editForm.payNo" class="w-full" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <label for="edit-source" class="font-medium text-sm mb-2 block">来源</label>
|
|
|
+ <InputText id="edit-source" v-model="editForm.source" class="w-full" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-end gap-3">
|
|
|
+ <Button label="取消" severity="secondary" @click="editDialog = false" />
|
|
|
+ <Button label="保存" severity="success" @click="saveEdit" :loading="editLoading" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import { useDateFormat } from '@vueuse/core'
|
|
|
+import Button from 'primevue/button'
|
|
|
+import Column from 'primevue/column'
|
|
|
+import DataTable from 'primevue/datatable'
|
|
|
+import DatePicker from 'primevue/calendar'
|
|
|
+import Dialog from 'primevue/dialog'
|
|
|
+import Dropdown from 'primevue/dropdown'
|
|
|
+import InputText from 'primevue/inputtext'
|
|
|
+import InputNumber from 'primevue/inputnumber'
|
|
|
+import { useConfirm } from 'primevue/useconfirm'
|
|
|
+import { useToast } from 'primevue/usetoast'
|
|
|
+import { listIncome, updateIncome, deleteIncome } from '@/services/api'
|
|
|
+import { IncomeType, OrderType } from '@/enums'
|
|
|
+
|
|
|
+const toast = useToast()
|
|
|
+const confirm = useConfirm()
|
|
|
+
|
|
|
+// 表格数据
|
|
|
+const tableData = ref({
|
|
|
+ content: [],
|
|
|
+ metadata: {
|
|
|
+ page: 0,
|
|
|
+ size: 20,
|
|
|
+ total: 0
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 加载状态
|
|
|
+const loading = ref(false)
|
|
|
+
|
|
|
+// 编辑相关
|
|
|
+const editDialog = ref(false)
|
|
|
+const editLoading = ref(false)
|
|
|
+const editForm = ref({
|
|
|
+ id: null,
|
|
|
+ agentName: null,
|
|
|
+ incomeAmount: null,
|
|
|
+ incomeType: null,
|
|
|
+ orderType: null,
|
|
|
+ price: null,
|
|
|
+ tipOrderId: null,
|
|
|
+ payChannel: null,
|
|
|
+ payNo: null,
|
|
|
+ video: null,
|
|
|
+ source: null
|
|
|
+})
|
|
|
+
|
|
|
+// 搜索表单
|
|
|
+const searchForm = ref({
|
|
|
+ id: null,
|
|
|
+ agentName: null,
|
|
|
+ incomeType: null,
|
|
|
+ orderType: null,
|
|
|
+ payChannel: null,
|
|
|
+ startDate: null,
|
|
|
+ endDate: null
|
|
|
+})
|
|
|
+
|
|
|
+// 收入类型选项
|
|
|
+const incomeTypeOptions = [
|
|
|
+ { label: '全部', value: null },
|
|
|
+ { label: IncomeType.tip, value: 'tip' },
|
|
|
+ { label: IncomeType.commission, value: 'commission' }
|
|
|
+]
|
|
|
+
|
|
|
+// 订单类型选项
|
|
|
+const orderTypeOptions = [
|
|
|
+ { label: '全部', value: null },
|
|
|
+ { label: OrderType.single_tip, value: 'single_tip' },
|
|
|
+ { label: OrderType.hourly_member, value: 'hourly_member' },
|
|
|
+ { label: OrderType.weekly_member, value: 'weekly_member' },
|
|
|
+ { label: OrderType.monthly_member, value: 'monthly_member' },
|
|
|
+ { label: OrderType.yearly_member, value: 'yearly_member' },
|
|
|
+ { label: OrderType.lifetime_member, value: 'lifetime_member' }
|
|
|
+]
|
|
|
+
|
|
|
+// 支付渠道选项
|
|
|
+const payChannelOptions = [
|
|
|
+ { label: '全部', value: null },
|
|
|
+ { label: '支付宝', value: 'alipay' },
|
|
|
+ { label: '微信支付', value: 'wechat' },
|
|
|
+ { label: '银行卡', value: 'bank' }
|
|
|
+]
|
|
|
+
|
|
|
+// 获取收入类型文本
|
|
|
+const getIncomeTypeText = (type) => {
|
|
|
+ return IncomeType[type] || type
|
|
|
+}
|
|
|
+
|
|
|
+// 获取收入类型样式类
|
|
|
+const getIncomeTypeClass = (type) => {
|
|
|
+ const classMap = {
|
|
|
+ tip: 'income-type-tip',
|
|
|
+ commission: 'income-type-commission'
|
|
|
+ }
|
|
|
+ return classMap[type] || ''
|
|
|
+}
|
|
|
+
|
|
|
+// 获取订单类型文本
|
|
|
+const getOrderTypeText = (type) => {
|
|
|
+ return OrderType[type] || type
|
|
|
+}
|
|
|
+
|
|
|
+// 获取支付渠道文本
|
|
|
+const getPayChannelText = (channel) => {
|
|
|
+ const channelMap = {
|
|
|
+ alipay: '支付宝',
|
|
|
+ wechat: '微信支付',
|
|
|
+ bank: '银行卡'
|
|
|
+ }
|
|
|
+ return channelMap[channel] || channel
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化金额
|
|
|
+const formatAmount = (amount) => {
|
|
|
+ if (!amount) return '0.00'
|
|
|
+ return Number(amount).toFixed(2)
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化日期时间
|
|
|
+const formatDateTime = (dateString) => {
|
|
|
+ if (!dateString) return '-'
|
|
|
+
|
|
|
+ const date = new Date(dateString)
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0')
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
+ const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
+
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
+}
|
|
|
+
|
|
|
+// 获取数据
|
|
|
+const fetchData = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const response = await listIncome(
|
|
|
+ tableData.value.metadata.page,
|
|
|
+ tableData.value.metadata.size,
|
|
|
+ searchForm.value.agentName || undefined,
|
|
|
+ searchForm.value.incomeType || undefined,
|
|
|
+ searchForm.value.startDate ? formatDateForAPI(searchForm.value.startDate) : undefined,
|
|
|
+ searchForm.value.endDate ? formatDateForAPI(searchForm.value.endDate) : undefined
|
|
|
+ )
|
|
|
+ tableData.value = response
|
|
|
+ } catch {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: '获取收入记录失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 分页处理
|
|
|
+const handlePageChange = (event) => {
|
|
|
+ tableData.value.metadata.page = event.page
|
|
|
+ tableData.value.metadata.size = event.rows
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索处理
|
|
|
+const handleSearch = () => {
|
|
|
+ tableData.value.metadata.page = 0
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+// 刷新处理
|
|
|
+const handleRefresh = () => {
|
|
|
+ searchForm.value = {
|
|
|
+ id: null,
|
|
|
+ agentName: null,
|
|
|
+ incomeType: null,
|
|
|
+ orderType: null,
|
|
|
+ payChannel: null,
|
|
|
+ startDate: null,
|
|
|
+ endDate: null
|
|
|
+ }
|
|
|
+ tableData.value.metadata.page = 0
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化日期用于API调用
|
|
|
+const formatDateForAPI = (date) => {
|
|
|
+ if (!date) return undefined
|
|
|
+ return useDateFormat(new Date(date), 'YYYY-MM-DD').value
|
|
|
+}
|
|
|
+
|
|
|
+// 确认删除
|
|
|
+const confirmDelete = (income) => {
|
|
|
+ confirm.require({
|
|
|
+ message: `确定要删除收入记录 "${income.agentName}" 吗?`,
|
|
|
+ header: '确认删除',
|
|
|
+ icon: 'pi pi-exclamation-triangle',
|
|
|
+ accept: () => deleteIncomeRecord(income.id)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 删除收入记录
|
|
|
+const deleteIncomeRecord = async (id) => {
|
|
|
+ try {
|
|
|
+ await deleteIncome(id)
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: '成功',
|
|
|
+ detail: '删除成功',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ fetchData()
|
|
|
+ } catch {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: '删除失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 复制到剪贴板
|
|
|
+const copyToClipboard = async (text) => {
|
|
|
+ try {
|
|
|
+ await navigator.clipboard.writeText(text)
|
|
|
+ } catch {
|
|
|
+ const textArea = document.createElement('textarea')
|
|
|
+ textArea.value = text
|
|
|
+ document.body.appendChild(textArea)
|
|
|
+ textArea.select()
|
|
|
+ document.execCommand('copy')
|
|
|
+ document.body.removeChild(textArea)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开编辑弹窗
|
|
|
+const openEditDialog = (income) => {
|
|
|
+ editForm.value = {
|
|
|
+ id: income.id,
|
|
|
+ agentName: income.agentName || null,
|
|
|
+ incomeAmount: income.incomeAmount || null,
|
|
|
+ incomeType: income.incomeType || null,
|
|
|
+ orderType: income.orderType || null,
|
|
|
+ price: income.price || null,
|
|
|
+ tipOrderId: income.tipOrderId || null,
|
|
|
+ payChannel: income.payChannel || null,
|
|
|
+ payNo: income.payNo || null,
|
|
|
+ video: income.video || null,
|
|
|
+ source: income.source || null
|
|
|
+ }
|
|
|
+ editDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 保存编辑
|
|
|
+const saveEdit = async () => {
|
|
|
+ editLoading.value = true
|
|
|
+ try {
|
|
|
+ await updateIncome(editForm.value.id, editForm.value)
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: '成功',
|
|
|
+ detail: '更新成功',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ editDialog.value = false
|
|
|
+ fetchData()
|
|
|
+ } catch {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: '更新失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ editLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化
|
|
|
+onMounted(() => {
|
|
|
+ fetchData()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.p-datatable-sm .p-datatable-tbody > tr > td {
|
|
|
+ padding: 0.5rem;
|
|
|
+ vertical-align: top;
|
|
|
+}
|
|
|
+
|
|
|
+.p-datatable-sm .p-datatable-thead > tr > th {
|
|
|
+ padding: 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.income-table {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.income-table .p-datatable-wrapper {
|
|
|
+ overflow-x: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.income-table .p-datatable-thead th {
|
|
|
+ white-space: nowrap;
|
|
|
+ min-width: 100px;
|
|
|
+}
|
|
|
+
|
|
|
+.font-mono {
|
|
|
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
|
+}
|
|
|
+
|
|
|
+.agent-name-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.amount-text {
|
|
|
+ color: #059669;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.income-type-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.income-type-tip {
|
|
|
+ color: #7c3aed;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.income-type-commission {
|
|
|
+ color: #f59e0b;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.order-type-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.order-id-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.price-text {
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-channel-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-no-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.video-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.source-text {
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.font-medium {
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.text-sm {
|
|
|
+ font-size: 0.875rem;
|
|
|
+}
|
|
|
+
|
|
|
+.copyable-text {
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.copyable-text:hover {
|
|
|
+ background-color: #e5e7eb;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.copyable-text:active {
|
|
|
+ background-color: #d1d5db;
|
|
|
+ transform: scale(0.98);
|
|
|
+}
|
|
|
+</style>
|