feat:钱包流水增加设备类型
fix:退款bug
This commit is contained in:
1 parent
0b26c6dbce
commit
4c3e46e2c1
6 files changed
+208
-105
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-select style="width: 100%" v-model="state.selectId" :placeholder="props.placeholder" :multiple="props.multiple"
|
||||
<el-select style="width: 100%" v-model="selectId" :placeholder="props.placeholder" :multiple="props.multiple"
|
||||
:collapse-tags="props.multiple" clearable remote-show-suffix filterable remote :remote-method="remoteSearch"
|
||||
:loading="state.loading" @visible-change="(e: boolean) => e && getOptions()" @change="change">
|
||||
:loading="state.loading" @visible-change="onVisibleChange" @change="change">
|
||||
<el-option v-for="v in state.options" :key="v.userId" :label="formatOptionLabel(v)"
|
||||
:value="getOptionValue(v)" :disabled="isOptionDisabled(v)">
|
||||
<div class="flex items-center">
|
||||
@@ -14,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="select-user">
|
||||
import { reactive } from "vue"
|
||||
import { reactive, watch } from "vue"
|
||||
import * as userApi from "/@/api/admin/user"
|
||||
|
||||
/** 选项变化事件,单选返回用户对象,多选返回用户对象数组。 */
|
||||
@@ -48,6 +48,8 @@ export type Props = {
|
||||
multiple?: boolean
|
||||
/** 组件绑定值使用用户 ID 还是用户手机号。 */
|
||||
valueField?: UserValueField
|
||||
/** 每页加载的用户数量。 */
|
||||
size?: number
|
||||
}
|
||||
|
||||
/** 用户选择器属性,未声明多选和取值字段时保持原有单选用户 ID 行为。 */
|
||||
@@ -56,78 +58,98 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
excludeUserIds: () => [],
|
||||
multiple: false,
|
||||
valueField: "userId",
|
||||
size: 10,
|
||||
})
|
||||
|
||||
/** 远程搜索条件,用户输入关键词后从第一页重新加载。 */
|
||||
const queryForm = reactive<any>({
|
||||
nameOrPhone: "",
|
||||
current: 1,
|
||||
size: 10,
|
||||
})
|
||||
|
||||
/** 选择器运行状态,选项在远程搜索时保留已选用户以维持标签展示。 */
|
||||
const state = reactive<any>({
|
||||
loading: true,
|
||||
selectId: selectId,
|
||||
options: [],
|
||||
loading: false,
|
||||
options: [] as UserOption[],
|
||||
})
|
||||
|
||||
watch(
|
||||
selectId,
|
||||
(newVal, oldValue) => {
|
||||
if (newVal == oldValue) return
|
||||
state.selectId = newVal
|
||||
getOptions()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
/** 请求序号:过期响应不更新选项,loading 只能由最新请求关闭。 */
|
||||
let requestSeq = 0
|
||||
/** 关闭 loading 的防抖动定时器,新请求开始时会先取消旧定时器。 */
|
||||
let loadingTimer: ReturnType<typeof setTimeout> | null = null
|
||||
/** 最近一次成功加载的列表查询参数;相同参数直接复用已有选项,避免重复调用接口。 */
|
||||
let lastListQueryKey = ""
|
||||
|
||||
/**
|
||||
* 加载下拉用户;多选模式额外回查已选值,避免远程搜索切换后已选标签退化为原始值。
|
||||
* 刷新下拉选项。
|
||||
*
|
||||
* <p>列表数据只在下拉展开或远程搜索时按需加载,且与最近一次成功参数一致时直接复用已有选项;
|
||||
* 绑定值变化只回查现有选项中缺失的已选用户。请求带序号保护:
|
||||
* 过期响应不更新选项,loading 只由最新请求延迟关闭,
|
||||
* 避免旧请求的关闭动作让仍在进行中的请求看起来已经完成。</p>
|
||||
*
|
||||
* @param fetchList 是否需要加载列表选项(下拉展开和远程搜索时为 true)
|
||||
*/
|
||||
async function getOptions() {
|
||||
async function refreshOptions(fetchList: boolean) {
|
||||
const missingSelectedValues = getMissingSelectedValues()
|
||||
const listQuery = { current: 1, size: props.size, nameOrPhone: queryForm.nameOrPhone || undefined }
|
||||
const listQueryKey = JSON.stringify(listQuery)
|
||||
const needList = fetchList && listQueryKey !== lastListQueryKey
|
||||
const needEcho = missingSelectedValues.length > 0
|
||||
// 列表已是最新且已选用户都能命中现有选项时无需任何请求,也不再闪一次 loading。
|
||||
if (!needList && !needEcho) {
|
||||
return
|
||||
}
|
||||
|
||||
const seq = ++requestSeq
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
// 只要有请求进行中就保持 loading,由最新完成的请求统一延迟关闭。
|
||||
state.loading = true
|
||||
try {
|
||||
state.loading = true
|
||||
if (props.multiple) {
|
||||
await getMultipleOptions()
|
||||
const [listOptions, echoOptions] = await Promise.all([
|
||||
needList ? fetchUserPage(listQuery) : Promise.resolve([] as UserOption[]),
|
||||
needEcho ? loadSelectedOptions(missingSelectedValues) : Promise.resolve([] as UserOption[]),
|
||||
])
|
||||
// 等待期间又有新请求进入时丢弃本次响应,避免旧结果覆盖新搜索。
|
||||
if (seq !== requestSeq) {
|
||||
return
|
||||
}
|
||||
const _pa = {
|
||||
...queryForm,
|
||||
userId: props.valueField === "userId" ? selectId.value : undefined,
|
||||
nameOrPhone: props.valueField === "phone" && selectId.value ? selectId.value : queryForm.nameOrPhone,
|
||||
if (needList) {
|
||||
lastListQueryKey = listQueryKey
|
||||
// 实时读取当前已选值,避免请求期间选择变化导致已选选项被新列表挤出。
|
||||
const selectedValueSet = new Set(getSelectedValues())
|
||||
const keepOptions = (state.options as UserOption[]).filter((item) =>
|
||||
selectedValueSet.has(String(getOptionValue(item))),
|
||||
)
|
||||
state.options = mergeOptions([...keepOptions, ...echoOptions, ...listOptions])
|
||||
} else {
|
||||
// 未重新拉取列表时保留现有选项,仅合并回查到的已选用户。
|
||||
state.options = mergeOptions([...(state.options as UserOption[]), ...echoOptions])
|
||||
}
|
||||
const { data } = await userApi.pageSelect(_pa)
|
||||
state.options = data.records ?? []
|
||||
} catch (error) {
|
||||
// 请求失败时保留现有选项;loading 由 finally 统一关闭,不中断组件交互。
|
||||
console.error("加载用户下拉选项失败", error)
|
||||
} finally {
|
||||
setTimeout(() => (state.loading = false), 500)
|
||||
// 只有最新请求允许关闭 loading;保留 500ms 延迟避免下拉数据闪动,新请求会先取消本定时器。
|
||||
if (seq === requestSeq) {
|
||||
loadingTimer = setTimeout(() => {
|
||||
state.loading = false
|
||||
loadingTimer = null
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载多选搜索结果和已选用户;手机号模式按每个历史手机号精确回显对应用户。
|
||||
*/
|
||||
async function getMultipleOptions() {
|
||||
const selectedValues = getSelectedValues()
|
||||
const selectedValueSet = new Set(selectedValues)
|
||||
const cachedSelectedOptions = (state.options as UserOption[]).filter((item) =>
|
||||
selectedValueSet.has(String(getOptionValue(item))),
|
||||
)
|
||||
const cachedValueSet = new Set(cachedSelectedOptions.map((item) => String(getOptionValue(item))))
|
||||
const missingSelectedValues = selectedValues.filter((value) => !cachedValueSet.has(value))
|
||||
const [searchResponse, selectedOptions] = await Promise.all([
|
||||
userApi.pageSelect({ ...queryForm }),
|
||||
loadSelectedOptions(missingSelectedValues),
|
||||
])
|
||||
state.options = mergeOptions([
|
||||
...cachedSelectedOptions,
|
||||
...selectedOptions,
|
||||
...(searchResponse.data?.records ?? []),
|
||||
])
|
||||
/** 调用用户选择分页接口并把返回记录规范为选项数组。 */
|
||||
async function fetchUserPage(query: Record<string, unknown>): Promise<UserOption[]> {
|
||||
const { data } = await userApi.pageSelect(query)
|
||||
return (data?.records ?? []) as UserOption[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 回查尚未缓存的已选用户,用户 ID 使用批量条件,手机号使用关键词查询后精确匹配。
|
||||
* 回查尚未缓存到选项列表的已选用户,用户 ID 使用批量条件,手机号使用关键词查询后精确匹配。
|
||||
*
|
||||
* @param selectedValues 尚未加载到选项列表的绑定值
|
||||
* @returns 与绑定值精确对应的用户选项
|
||||
@@ -135,21 +157,28 @@ async function getMultipleOptions() {
|
||||
async function loadSelectedOptions(selectedValues: string[]): Promise<UserOption[]> {
|
||||
if (!selectedValues.length) return []
|
||||
if (props.valueField === "userId") {
|
||||
const { data } = await userApi.pageSelect({
|
||||
return fetchUserPage({
|
||||
userIds: selectedValues,
|
||||
current: 1,
|
||||
size: Math.max(queryForm.size, selectedValues.length),
|
||||
size: Math.max(props.size, selectedValues.length),
|
||||
})
|
||||
return data.records ?? []
|
||||
}
|
||||
const responses = await Promise.all(
|
||||
selectedValues.map((phone) => userApi.pageSelect({ current: 1, size: 10, nameOrPhone: phone })),
|
||||
selectedValues.map((phone) => fetchUserPage({ current: 1, size: 10, nameOrPhone: phone })),
|
||||
)
|
||||
return responses
|
||||
.flatMap(({ data }) => data.records ?? [])
|
||||
.flatMap((records) => records)
|
||||
.filter((item: UserOption) => selectedValues.includes(String(item.phone || "")))
|
||||
}
|
||||
|
||||
/** 计算尚未加载到选项列表的绑定值,用于按需回查已选用户。 */
|
||||
function getMissingSelectedValues(): string[] {
|
||||
const selectedValues = getSelectedValues()
|
||||
if (!selectedValues.length) return []
|
||||
const knownValueSet = new Set((state.options as UserOption[]).map((item) => String(getOptionValue(item))))
|
||||
return selectedValues.filter((value) => !knownValueSet.has(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户 ID 合并远程搜索和已选用户,防止相同用户在下拉中重复展示。
|
||||
*
|
||||
@@ -166,49 +195,48 @@ function mergeOptions(options: UserOption[]): UserOption[] {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前绑定值规范为字符串数组,供多选回显和选项匹配使用。
|
||||
*
|
||||
* @returns 去除空值后的已选用户 ID 或手机号集合
|
||||
*/
|
||||
/** 处理远程搜索关键词变化,避免相同关键词重复请求。 */
|
||||
function remoteSearch(params: string) {
|
||||
if (params != queryForm.nameOrPhone) {
|
||||
queryForm.nameOrPhone = params
|
||||
refreshOptions(true)
|
||||
}
|
||||
}
|
||||
|
||||
/** 下拉展开时按需加载列表选项;收起时不处理。 */
|
||||
function onVisibleChange(visible: boolean) {
|
||||
if (visible) {
|
||||
refreshOptions(true)
|
||||
}
|
||||
}
|
||||
|
||||
/** 绑定值变化时只补齐缺失的已选用户回显,不重新拉取列表。 */
|
||||
watch(
|
||||
selectId,
|
||||
(newVal, oldValue) => {
|
||||
if (newVal == oldValue) return
|
||||
refreshOptions(false)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
/** 将当前绑定值规范为字符串数组,供多选回显和选项匹配使用。 */
|
||||
function getSelectedValues(): string[] {
|
||||
const values = Array.isArray(selectId.value) ? selectId.value : [selectId.value]
|
||||
return values.filter((value) => value !== undefined && value !== null && value !== "").map(String)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按当前取值模式返回选项绑定值。
|
||||
*
|
||||
* @param user 用户下拉选项
|
||||
* @returns 用户 ID 或手机号;手机号缺失时返回空字符串并禁用该选项
|
||||
*/
|
||||
/** 按当前取值模式返回选项绑定值。 */
|
||||
function getOptionValue(user: UserOption): string | number {
|
||||
return props.valueField === "phone" ? user.phone || "" : user.userId
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户选项标签,手机号取值模式会明确提示用户尚未填写手机号。
|
||||
*
|
||||
* @param user 用户下拉选项
|
||||
* @returns 姓名及可用手机号组成的展示文本
|
||||
*/
|
||||
/** 生成选项标签,手机号取值模式会明确提示用户尚未填写手机号。 */
|
||||
function formatOptionLabel(user: UserOption): string {
|
||||
if (user.phone) return `${user.name}-${user.phone}`
|
||||
return props.valueField === "phone" ? `${user.name}-未填写手机号` : user.name
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理远程搜索关键词变化,避免相同关键词重复请求。
|
||||
*
|
||||
* @param params 姓名、手机号或用户名关键词
|
||||
*/
|
||||
function remoteSearch(params: string) {
|
||||
if (params != queryForm.nameOrPhone) {
|
||||
queryForm.nameOrPhone = params
|
||||
getOptions()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向调用方返回完整用户选项;多选返回选项数组,单选保持原有单个用户对象行为。
|
||||
*
|
||||
@@ -217,23 +245,20 @@ function remoteSearch(params: string) {
|
||||
function change(v: any) {
|
||||
if (props.multiple) {
|
||||
const selectedValueSet = new Set((Array.isArray(v) ? v : []).map(String))
|
||||
const targetUsers = state.options.filter((item: UserOption) =>
|
||||
const targetUsers = (state.options as UserOption[]).filter((item) =>
|
||||
selectedValueSet.has(String(getOptionValue(item))),
|
||||
)
|
||||
emit("change", targetUsers)
|
||||
return
|
||||
}
|
||||
const targetUser = state.options.find((item: UserOption) => String(getOptionValue(item)) === String(v))
|
||||
const targetUser = (state.options as UserOption[]).find(
|
||||
(item) => String(getOptionValue(item)) === String(v),
|
||||
)
|
||||
emit("change", targetUser)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否不可选;手机号取值模式下未填写手机号的用户不能作为监护人值提交。
|
||||
*
|
||||
* @param user 用户下拉选项
|
||||
* @returns 是否禁用当前选项
|
||||
*/
|
||||
function isOptionDisabled(user: UserOption) {
|
||||
/** 判断是否不可选;手机号取值模式下未填写手机号的用户不能作为监护人值提交。 */
|
||||
function isOptionDisabled(user: UserOption): boolean {
|
||||
const isExcluded = props.excludeUserIds.some((id) => String(id) === String(user.userId))
|
||||
return isExcluded || (props.valueField === "phone" && !user.phone)
|
||||
}
|
||||
|
||||
@@ -26,14 +26,18 @@
|
||||
start-placeholder="开始时间" end-placeholder="结束时间" :clearable="false" />
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb-3">
|
||||
<div class="add-bar__label mb-1">可预约日期</div>
|
||||
<div class="flex w-full flex-wrap items-center gap-2">
|
||||
<el-date-picker v-model="addBar.dateRange" type="daterange" class="min-w-0 flex-1"
|
||||
value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" />
|
||||
<el-tooltip placement="top" content="用户在这些日期到店用餐;每个日期一天一行,添加后还可在表格中单独调整餐段">
|
||||
<div class="add-bar__label mb-1 cursor-help">可预约日期(?)</div>
|
||||
</el-tooltip>
|
||||
<el-date-picker v-model="addBar.dateRange" type="daterange" class="w-full"
|
||||
value-format="YYYY-MM-DD" start-placeholder="用餐开始日期" end-placeholder="用餐结束日期" />
|
||||
<div class="mt-2 flex w-full flex-nowrap items-center gap-2 whitespace-nowrap">
|
||||
<span class="text-xs text-[var(--el-text-color-secondary)]">快捷生成:结束预约次日起,连续</span>
|
||||
<el-input-number v-model="addBar.pushDays" :min="1" :max="31" size="small"
|
||||
controls-position="right" class="w-24" aria-label="向后推天数" />
|
||||
<el-tooltip content="以结束预约时间的次日起,连续生成 N 天可预约日期" placement="top">
|
||||
<el-button size="small" icon="MagicStick" @click="generateDateRange">生成</el-button>
|
||||
controls-position="right" class="w-20" aria-label="向后推天数" />
|
||||
<span class="text-xs text-[var(--el-text-color-secondary)]">天</span>
|
||||
<el-tooltip content="按左侧开放预约时间的结束时间次日起算,自动填入上方的日期范围" placement="top">
|
||||
<el-button size="small" icon="MagicStick" @click="generateDateRange">生成日期</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-col>
|
||||
@@ -292,7 +296,13 @@ const generateDateRange = () => {
|
||||
}
|
||||
const days = Math.max(1, Math.min(MAX_DAYS, Number(addBar.pushDays) || 1))
|
||||
const start = dayjs(endTime).startOf("day").add(1, "day")
|
||||
addBar.dateRange = [start.format("YYYY-MM-DD"), start.add(days - 1, "day").format("YYYY-MM-DD")]
|
||||
const end = start.add(days - 1, "day")
|
||||
addBar.dateRange = [start.format("YYYY-MM-DD"), end.format("YYYY-MM-DD")]
|
||||
useMessage().success(
|
||||
days > 1
|
||||
? `已生成 ${start.format("M月D日")} ~ ${end.format("M月D日")} 共 ${days} 天可预约日期`
|
||||
: `已生成 ${start.format("M月D日")} 1 天可预约日期`,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
|
||||
/** 设备类型筛选白名单:仅保留钱包流水实际消费的设备(咖啡机/食堂/云版消费机)。 */
|
||||
const SYS_TYPE_FILTER_VALUES = ["13", "2", "9"]
|
||||
|
||||
/**
|
||||
* 钱包流水页共用的设备类型筛选项:按白名单过滤 hard_type 字典。
|
||||
*
|
||||
* 设备类型仅对订单支付/退款流水有意义(hard_type 固化列),消费、退款等
|
||||
* 订单类流水页面统一复用本选项;充值、记账、退还类流水 hard_type 恒空,不提供该筛选。
|
||||
*/
|
||||
export function useSysTypeOptions() {
|
||||
const { hard_type } = useDict("hard_type")
|
||||
/** 设备类型筛选项:按白名单过滤后的字典项。 */
|
||||
const sysTypeOptions = computed(() =>
|
||||
(hard_type.value || []).filter((item: any) => SYS_TYPE_FILTER_VALUES.includes(item.value))
|
||||
)
|
||||
return { sysTypeOptions }
|
||||
}
|
||||
@@ -18,6 +18,13 @@
|
||||
<el-form-item style="width: 300px" label="用户" prop="userId">
|
||||
<select-user v-model="state.queryForm.userId" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 260px" label="设备类型" prop="sysTypes">
|
||||
<el-select v-model="state.queryForm.sysTypes" multiple clearable filterable
|
||||
collapse-tags collapse-tags-tooltip placeholder="全部">
|
||||
<el-option v-for="option in sysTypeOptions" :key="option.value"
|
||||
:label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 300px" label="退款时间" prop="createTimeList">
|
||||
<el-date-picker v-model="state.queryForm.createTimeList" value-format="YYYY-MM-DD" type="daterange" />
|
||||
</el-form-item>
|
||||
@@ -62,6 +69,11 @@
|
||||
<dict-tag :options="wallet_biz" :value="row.bizType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sysTypeName" width="120" label="设备类型">
|
||||
<template #default="{ row }">
|
||||
{{ row.sysTypeName || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" align="center" width="200" label="退款金额">
|
||||
<template #default="{ row }">
|
||||
<div class="color-success">{{ displayPrice(row.price) }}</div>
|
||||
@@ -123,13 +135,14 @@
|
||||
<script setup lang="ts" name="systemWalletRecharge">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { pageTk, tkTotal, submitTkExport } from "/@/api/wallet/wallet-transaction"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useMessage } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import * as hardAfterSaleApi from "/@/api/hard/hard-after-sale"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
import { useSysTypeOptions } from "../composables/useSysTypeOptions"
|
||||
|
||||
const showDrawer = ref(false)
|
||||
const balanceStatisticsLoading = ref(false)
|
||||
@@ -145,6 +158,8 @@ const TransactionRemarkForm = defineAsyncComponent(() => import("../components/t
|
||||
const TransactionInfoForm = defineAsyncComponent(() => import("../components/transaction-info-form.vue"))
|
||||
// 定义查询字典
|
||||
const { wallet_type, wallet_biz } = useDict("wallet_type", "wallet_biz")
|
||||
/** 设备类型筛选项:白名单过滤后的 hard_type 字典(咖啡机/食堂/云版消费机)。 */
|
||||
const { sysTypeOptions } = useSysTypeOptions()
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
/** 通用流水备注编辑弹窗引用。 */
|
||||
@@ -167,6 +182,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
studentInfo: createEmptyUserIdentityStudentQuery(),
|
||||
teacherInfo: createEmptyUserIdentityTeacherQuery(),
|
||||
hasFace: "",
|
||||
sysTypes: [],
|
||||
},
|
||||
pageList: pageTk,
|
||||
})
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
:label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 260px" label="设备类型" prop="sysTypes">
|
||||
<el-select v-model="state.queryForm.sysTypes" multiple clearable filterable
|
||||
collapse-tags collapse-tags-tooltip placeholder="全部">
|
||||
<el-option v-for="option in sysTypeOptions" :key="option.value"
|
||||
:label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 300px" label="流水时间" prop="createTimeList">
|
||||
<el-date-picker v-model="state.queryForm.createTimeList" value-format="YYYY-MM-DD"
|
||||
type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" />
|
||||
@@ -74,6 +81,11 @@
|
||||
<dict-tag :options="wallet_biz" :value="row.bizType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sysTypeName" width="120" label="设备类型">
|
||||
<template #default="{ row }">
|
||||
{{ row.sysTypeName || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="title" label="流水标题" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="price" width="160" align="center" label="交易金额">
|
||||
<template #default="{ row }">
|
||||
@@ -97,9 +109,11 @@
|
||||
v-auths="['wallet_walletTransaction_edit', 'wallet_walletTransaction_update_info']">
|
||||
<template #default="{ row }">
|
||||
<div class="row-actions">
|
||||
<el-button icon="EditPen" link type="primary" v-auth="'wallet_walletTransaction_edit'"
|
||||
<el-button icon="EditPen" link type="primary"
|
||||
v-auth="'wallet_walletTransaction_edit'"
|
||||
@click="remarkFormRef?.openDialog(row)">修改备注</el-button>
|
||||
<el-button icon="Edit" link type="primary" v-auth="'wallet_walletTransaction_update_info'"
|
||||
<el-button icon="Edit" link type="primary"
|
||||
v-auth="'wallet_walletTransaction_update_info'"
|
||||
@click="infoFormRef?.openDialog(row)">修改信息</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -128,6 +142,7 @@ import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
import { useSysTypeOptions } from "../composables/useSysTypeOptions"
|
||||
|
||||
/** 所有钱包流水列表共用的备注编辑表单。 */
|
||||
const TransactionRemarkForm = defineAsyncComponent(() => import("../components/transaction-remark-form.vue"))
|
||||
@@ -142,6 +157,8 @@ const transactionTypeOptions = [
|
||||
|
||||
/** 页面展示交易类型、钱包类型和操作标签所需的字典数据。 */
|
||||
const { wallet_type, wallet_biz, wallet_transaction_tag } = useDict("wallet_type", "wallet_biz", "wallet_transaction_tag")
|
||||
/** 设备类型筛选项:白名单过滤后的 hard_type 字典(咖啡机/食堂/云版消费机)。 */
|
||||
const { sysTypeOptions } = useSysTypeOptions()
|
||||
/** 搜索面板引用,用于执行 Element Plus 表单重置。 */
|
||||
const queryRef = ref()
|
||||
/** 部门树引用,用于重置已选部门和包含模式。 */
|
||||
@@ -161,6 +178,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
teacherInfo: createEmptyUserIdentityTeacherQuery(),
|
||||
hasFace: "",
|
||||
tagList: [],
|
||||
sysTypes: [],
|
||||
},
|
||||
pageList: pageAll,
|
||||
})
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
<el-form-item style="width: 200px" label="充值方式" prop="bizType">
|
||||
<dict-select v-model="state.queryForm.bizType" dict-code="wallet_biz" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 260px" label="设备类型" prop="sysTypes">
|
||||
<el-select v-model="state.queryForm.sysTypes" multiple clearable filterable
|
||||
collapse-tags collapse-tags-tooltip placeholder="全部">
|
||||
<el-option v-for="option in sysTypeOptions" :key="option.value"
|
||||
:label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 300px" label="消费时间" prop="createTimeList">
|
||||
<el-date-picker v-model="state.queryForm.createTimeList" value-format="YYYY-MM-DD" type="daterange" />
|
||||
</el-form-item>
|
||||
@@ -65,6 +72,11 @@
|
||||
<dict-tag :options="wallet_biz" :value="row.bizType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sysTypeName" width="120" label="设备类型">
|
||||
<template #default="{ row }">
|
||||
{{ row.sysTypeName || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" align="center" width="200" label="消费金额">
|
||||
<template #default="{ row }">
|
||||
<div class="color-danger">{{ displayPrice(row.price) }}</div>
|
||||
@@ -132,10 +144,10 @@ import { pageXf, xfTotal, submitXfExport } from "/@/api/wallet/wallet-transactio
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import { deptTree } from "/@/api/admin/dept"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
import { useSysTypeOptions } from "../composables/useSysTypeOptions"
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
@@ -154,6 +166,8 @@ const TransactionRemarkForm = defineAsyncComponent(() => import("../components/t
|
||||
const TransactionInfoForm = defineAsyncComponent(() => import("../components/transaction-info-form.vue"))
|
||||
// 定义查询字典
|
||||
const { wallet_type, wallet_biz } = useDict("wallet_type", "wallet_biz")
|
||||
/** 设备类型筛选项:白名单过滤后的 hard_type 字典(咖啡机/食堂/云版消费机)。 */
|
||||
const { sysTypeOptions } = useSysTypeOptions()
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
/** 通用流水备注编辑弹窗引用。 */
|
||||
@@ -174,6 +188,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
studentInfo: createEmptyUserIdentityStudentQuery(),
|
||||
teacherInfo: createEmptyUserIdentityTeacherQuery(),
|
||||
hasFace: "",
|
||||
sysTypes: [],
|
||||
},
|
||||
pageList: pageXf,
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user