处理节假日列表

This commit is contained in:
2026-05-25 09:22:14 +08:00
parent 8ef911603e
commit 5c8204706e
13 changed files with 451 additions and 286 deletions

View File

@ -52,6 +52,7 @@ export interface EditUsernameParams {
// 验证码修改密码参数类型 // 验证码修改密码参数类型
export interface UpdatePwdByCaptchaParams { export interface UpdatePwdByCaptchaParams {
mobile_area?: string
verify_type: number verify_type: number
code: string code: string
login_password1: string login_password1: string

View File

@ -8,6 +8,7 @@ export interface UserInfo {
mobile: string mobile: string
role_id: number role_id: number
pass_admin: string pass_admin: string
last_login?: string
salt_code_admin: string salt_code_admin: string
real_check: number real_check: number
par_uid: number par_uid: number

View File

@ -20,7 +20,7 @@
defineOptions({ defineOptions({
name: 'Currency' name: 'Currency'
}) })
import FundDetailHeader from '@/components/common/header.vue' import FundDetailHeader from '@/components/common/Header.vue'
import FundDetailTable from '@/components/common/Table.vue' import FundDetailTable from '@/components/common/Table.vue'
import { usePageLoading, useButtonLoading } from '@/composables/useLoading' import { usePageLoading, useButtonLoading } from '@/composables/useLoading'
import FundDetailDialog from '@/components/common/Dialog.vue' import FundDetailDialog from '@/components/common/Dialog.vue'
@ -46,8 +46,8 @@ interface HeaderButton {
const { loading, startLoading, stopLoading } = usePageLoading() const { loading, startLoading, stopLoading } = usePageLoading()
const { buttonLoading, startButtonLoading, stopButtonLoading } = useButtonLoading() const { buttonLoading, startButtonLoading, stopButtonLoading } = useButtonLoading()
const buttonsOptions = ref(buttons) const buttonsOptions = ref<any>(buttons)
const formItems = ref(form) const formItems = ref<any>(form)
const tableData = ref([]) const tableData = ref([])
const tableColumnsOptions = ref(tableColumns) const tableColumnsOptions = ref(tableColumns)
const dialogTitle = ref('') const dialogTitle = ref('')
@ -79,7 +79,7 @@ const getTableList = async () => {
try { try {
startLoading() startLoading()
const data = await getCurrencyList() const data:any = await getCurrencyList()
tableData.value = (data || []).map((item: any) => { tableData.value = (data || []).map((item: any) => {
return { return {

View File

@ -53,7 +53,7 @@ const getEnumDicOptions = async () => {
const res: any = await getEnumDicOptionsApi() const res: any = await getEnumDicOptionsApi()
const typeIndex = searchOptions.value.findIndex((item: any) => item.prop === 'type') const typeIndex = searchOptions.value.findIndex((item: any) => item.prop === 'type')
if (typeIndex !== -1 && res) { if (typeIndex !== -1 && res) {
searchOptions.value[typeIndex].options = res.map((item: any) => ({ searchOptions.value[typeIndex]!.options = res.map((item: any) => ({
label: item.value, label: item.value,
value: item.id value: item.id
})) }))

View File

@ -186,7 +186,7 @@ const tableRef = ref()
const handleSelectionChange = (rows: TableRow[]) => { const handleSelectionChange = (rows: TableRow[]) => {
if (rows.length > 1) { if (rows.length > 1) {
// 1 // 1
const lastRow = rows[rows.length - 1] const lastRow:any = rows[rows.length - 1]
// //
tableRef.value?.clearSelection() tableRef.value?.clearSelection()
// //
@ -204,7 +204,7 @@ const handleApprove = async () => {
return return
} }
try { try {
await ElMessageBox.confirm(`是否确认通过该审核?订单号:${selectedRows.value[0].orderNo}`, '审核确认', { await ElMessageBox.confirm(`是否确认通过该审核?订单号:${selectedRows.value[0]?.orderNo}`, '审核确认', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
@ -215,7 +215,7 @@ const handleApprove = async () => {
startLoading() startLoading()
try { try {
await reviewRechargeOrder({ id: selectedRows.value[0].id, status: '3' }) await reviewRechargeOrder({ id: selectedRows.value[0]?.id, status: '3' })
await getList() await getList()
} finally { } finally {
stopLoading() stopLoading()
@ -229,7 +229,7 @@ const handleReject = async () => {
return return
} }
try { try {
await ElMessageBox.confirm(`是否确认驳回该审核?订单号:${selectedRows.value[0].orderNo}`, '审核确认', { await ElMessageBox.confirm(`是否确认驳回该审核?订单号:${selectedRows.value[0]?.orderNo}`, '审核确认', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
@ -241,7 +241,7 @@ const handleReject = async () => {
//:1,2,3,4 //:1,2,3,4
startLoading() startLoading()
try { try {
await reviewRechargeOrder({ id: selectedRows.value[0].id, status: '4' }) await reviewRechargeOrder({ id: selectedRows.value[0]?.id, status: '4' })
await getList() await getList()
} finally { } finally {
stopLoading() stopLoading()

View File

@ -149,7 +149,7 @@ const transferType = ref('internal')
/** /**
* @description 创建默认转账表单数据 * @description 创建默认转账表单数据
*/ */
const createDefaultTransferForm = () => ({ const createDefaultTransferForm = ():any => ({
transferOutAccount: -1, transferOutAccount: -1,
transferInAccount: '', transferInAccount: '',
transferAmount: '', transferAmount: '',
@ -206,7 +206,7 @@ watch(
transferFormOptionsMap.value.transferInAccount = transferFormOptionsMap.value.transferInAccount =
transferFormOptionsMap.value.transferOutAccount.filter((item: any) => item.value !== val) transferFormOptionsMap.value.transferOutAccount.filter((item: any) => item.value !== val)
}, },
{ inmediate: true, deep: true }, { immediate: true, deep: true },
) )
const transferFormRules = ref(rules(transferForm.value)) const transferFormRules = ref(rules(transferForm.value))

View File

@ -59,7 +59,7 @@ const { loading, startLoading, stopLoading } = usePageLoading()
const total = ref(0) const total = ref(0)
const withdrawDialogVisible = ref(false) const withdrawDialogVisible = ref(false)
const currentChannelId = ref<number>() const currentChannelId = ref<number>()
const currentChannelInfo = ref<{ id: number; name: string; currency?: string }>() const currentChannelInfo = ref<any>()
const queryParams = reactive({ const queryParams = reactive({
page: 1, page: 1,

View File

@ -13,11 +13,11 @@ export const tableColumns = [
placeholder: '请选择入金货币', placeholder: '请选择入金货币',
}, },
// 接口地址 // 接口地址
{ label: '接口地址', prop: 'api_url' }, // { label: '接口地址', prop: 'api_url' },
// 接口appid // // 接口appid
{ label: '接口appid', prop: 'api_appid' }, // { label: '接口appid', prop: 'api_appid' },
// 状态 // // 状态
{ label: '状态', prop: 'statusStr' }, // { label: '状态', prop: 'statusStr' },
// 操作 // 操作
{ label: '操作', prop: 'action', width: 100, slotName: 'action' as const }, { label: '操作', prop: 'action', width: 100, slotName: 'action' as const },
] ]

View File

@ -64,7 +64,7 @@ type ChannelType = {
channel_code: string channel_code: string
} }
const getStatusText = (status: number | string) => { const getStatusText = (status: any) => {
return ['', '正常', '禁用'][status] return ['', '正常', '禁用'][status]
} }
@ -73,7 +73,7 @@ const { loading, startLoading, stopLoading } = usePageLoading()
const { buttonLoading: submitLoading, startButtonLoading: startSubmitLoading, stopButtonLoading: stopSubmitLoading } = useButtonLoading() const { buttonLoading: submitLoading, startButtonLoading: startSubmitLoading, stopButtonLoading: stopSubmitLoading } = useButtonLoading()
const initData = async () => { const initData = async () => {
startLoading() startLoading()
const res = await getChannelWithdrawList() const res:any = await getChannelWithdrawList()
stopLoading() stopLoading()
console.log(res) console.log(res)
tableData.value = res.map((item: ChannelType) => ({ tableData.value = res.map((item: ChannelType) => ({
@ -84,13 +84,13 @@ const initData = async () => {
} }
// //
const tableColumnsOptions = ref(tableColumns) const tableColumnsOptions = ref<any>(tableColumns)
const tableData = ref([]) const tableData = ref<any>([])
const tableRef = ref() const tableRef = ref()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const dialogTitleType = ref(1) const dialogTitleType = ref(1)
const formData = ref({ const formData = ref<any>({
name: '', name: '',
channel_fee: null, channel_fee: null,
currency_id: null, currency_id: null,
@ -100,7 +100,7 @@ const formData = ref({
channel_code: '', channel_code: '',
status: 1, status: 1,
}) })
const formItems = ref(formItemsData) const formItems = ref<any>(formItemsData)
const formRules = ref(formRulesData) const formRules = ref(formRulesData)
const optionsMap = ref(optionsMapData) const optionsMap = ref(optionsMapData)
const submitDetail = async () => { const submitDetail = async () => {
@ -180,7 +180,7 @@ const clearSelection = () => {
onMounted(() => { onMounted(() => {
initData() initData()
// //
getChannelList().then((data) => { getChannelList().then((data:any) => {
console.log(data) console.log(data)
optionsMap.value.currency_id = (data || []).map((item: any) => ({ optionsMap.value.currency_id = (data || []).map((item: any) => ({
value: item.id, value: item.id,

View File

@ -40,7 +40,7 @@
<el-icon :size="32"><UserFilled /></el-icon> <el-icon :size="32"><UserFilled /></el-icon>
</div> </div>
<div class="user-details"> <div class="user-details">
<span class="user-name">{{ userStore.userInfo.username }}</span> <span class="user-name">{{ userStore.userInfo?.username || '-' }}</span>
<!-- <el-tag size="small" :type="+userStore.userInfo.role_id === 1 ? 'danger' : 'primary'"> <!-- <el-tag size="small" :type="+userStore.userInfo.role_id === 1 ? 'danger' : 'primary'">
{{ userStore.userInfo.role }} {{ userStore.userInfo.role }}
</el-tag> --> </el-tag> -->
@ -48,11 +48,11 @@
</div> </div>
<div class="info-row"> <div class="info-row">
<span class="info-label">用户ID</span> <span class="info-label">用户ID</span>
<span class="info-value">{{ userStore.userInfo.id }}</span> <span class="info-value">{{ userStore.userInfo?.id || '-' }}</span>
</div> </div>
<div class="info-row"> <div class="info-row">
<span class="info-label">最后登录</span> <span class="info-label">最后登录</span>
<span class="info-value">{{ userStore.userInfo.last_login }}</span> <span class="info-value">{{ userStore.userInfo?.last_login || '-' }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -197,7 +197,9 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.dashboard { .dashboard {
padding: 24px; padding: 20px;
max-width: 1600px;
margin: 0 auto;
} }
.page-header { .page-header {
@ -209,7 +211,7 @@ onMounted(() => {
border-bottom: 1px solid var(--el-border-color-light); border-bottom: 1px solid var(--el-border-color-light);
h1 { h1 {
font-size: 20px; font-size: 22px;
font-weight: 600; font-weight: 600;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
margin: 0; margin: 0;
@ -227,32 +229,45 @@ onMounted(() => {
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(4, 1fr);
gap: 16px; gap: 20px;
@media (max-width: 1200px) {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 900px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
} }
.stat-box { .stat-box {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
padding: 20px; padding: 20px 24px;
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
border: 1px solid var(--el-border-color-lighter); border: 1px solid var(--el-border-color-lighter);
transition: all 0.2s ease; transition: all 0.25s ease;
&:hover { &:hover {
border-color: var(--el-color-primary-light-5); border-color: var(--el-color-primary-light-5);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
transform: translateY(-2px);
} }
&__icon { &__icon {
width: 48px; width: 52px;
height: 48px; height: 52px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 10px; border-radius: 12px;
flex-shrink: 0; flex-shrink: 0;
} }
@ -264,8 +279,8 @@ onMounted(() => {
} }
&__value { &__value {
font-size: 20px; font-size: 24px;
font-weight: 600; font-weight: 700;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
line-height: 1.2; line-height: 1.2;
max-width: 100%; max-width: 100%;
@ -275,24 +290,28 @@ onMounted(() => {
} }
&__label { &__label {
font-size: 13px; font-size: 14px;
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
margin-top: 4px; margin-top: 6px;
} }
} }
.stat-box--0 .stat-box__icon { background: #ecf5ff; color: #409eff; } .stat-box--0 .stat-box__icon { background: linear-gradient(135deg, #ecf5ff, #d9ecff); color: #409eff; }
.stat-box--1 .stat-box__icon { background: #f0f9eb; color: #67c23a; } .stat-box--1 .stat-box__icon { background: linear-gradient(135deg, #f0f9eb, #e1f3d8); color: #67c23a; }
.stat-box--2 .stat-box__icon { background: #fdf6ec; color: #e6a23c; } .stat-box--2 .stat-box__icon { background: linear-gradient(135deg, #fdf6ec, #fbeccb); color: #e6a23c; }
.stat-box--3 .stat-box__icon { background: #fef0f0; color: #f56c6c; } .stat-box--3 .stat-box__icon { background: linear-gradient(135deg, #fef0f0, #fad7d7); color: #f56c6c; }
.stat-box--4 .stat-box__icon { background: #f4f4f5; color: #909399; } .stat-box--4 .stat-box__icon { background: linear-gradient(135deg, #f4f4f5, #e4e4e7); color: #909399; }
.stat-box--5 .stat-box__icon { background: #fdf5f6; color: #c71585; } .stat-box--5 .stat-box__icon { background: linear-gradient(135deg, #fdf5f6, #f9d7e3); color: #c71585; }
.stat-box--6 .stat-box__icon { background: #f0f9ff; color: #0089e5; } .stat-box--6 .stat-box__icon { background: linear-gradient(135deg, #f0f9ff, #d6ecff); color: #0089e5; }
.content-area { .content-area {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: 1fr 1fr;
gap: 16px; gap: 20px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
} }
.info-panel { .info-panel {
@ -305,12 +324,12 @@ onMounted(() => {
.panel-header { .panel-header {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
padding: 14px 16px; padding: 16px 20px;
background: var(--el-fill-color-light); background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter); border-bottom: 1px solid var(--el-border-color-lighter);
font-size: 14px; font-size: 15px;
font-weight: 500; font-weight: 600;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
.el-icon { .el-icon {
@ -319,24 +338,24 @@ onMounted(() => {
} }
.panel-content { .panel-content {
padding: 16px; padding: 20px;
} }
.user-info { .user-info {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 14px; gap: 16px;
padding-bottom: 14px; padding-bottom: 16px;
margin-bottom: 14px; margin-bottom: 16px;
border-bottom: 1px dashed var(--el-border-color-lighter); border-bottom: 1px dashed var(--el-border-color-lighter);
.user-avatar { .user-avatar {
width: 48px; width: 52px;
height: 48px; height: 52px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--el-color-primary-light-9); background: linear-gradient(135deg, var(--el-color-primary-light-9), var(--el-color-primary-light-7));
border-radius: 50%; border-radius: 50%;
color: var(--el-color-primary); color: var(--el-color-primary);
} }
@ -347,7 +366,7 @@ onMounted(() => {
gap: 6px; gap: 6px;
.user-name { .user-name {
font-size: 15px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
@ -358,7 +377,7 @@ onMounted(() => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 0; padding: 12px 0;
border-bottom: 1px dashed var(--el-border-color-lighter); border-bottom: 1px dashed var(--el-border-color-lighter);
&:last-child { &:last-child {
@ -366,13 +385,13 @@ onMounted(() => {
} }
.info-label { .info-label {
font-size: 13px; font-size: 14px;
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.info-value { .info-value {
font-size: 13px; font-size: 14px;
font-weight: 500; font-weight: 600;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
} }
@ -380,17 +399,17 @@ onMounted(() => {
.action-list { .action-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 10px;
} }
.action-item { .action-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 12px;
padding: 10px 12px; padding: 12px 14px;
background: var(--el-fill-color-light); background: var(--el-fill-color-light);
border-radius: 8px; border-radius: 10px;
font-size: 13px; font-size: 14px;
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
@ -399,6 +418,10 @@ onMounted(() => {
&:hover { &:hover {
background: var(--el-color-primary-light-9); background: var(--el-color-primary-light-9);
color: var(--el-color-primary); color: var(--el-color-primary);
.arrow {
opacity: 1;
}
} }
.el-icon { .el-icon {
@ -409,7 +432,8 @@ onMounted(() => {
.arrow { .arrow {
margin-left: auto; margin-left: auto;
font-size: 12px; font-size: 12px;
opacity: 0.5; opacity: 0.4;
transition: opacity 0.2s ease;
} }
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="personal-center table_form-container"> <div class="personal-center table_form-container" v-loading="loading">
<div class="page-header"> <div class="page-header">
<h2>{{ t('personalCenter.pageTitle') }}</h2> <h2>{{ t('personalCenter.pageTitle') }}</h2>
</div> </div>
@ -174,7 +174,9 @@ import {
updatePwdByCaptchaApi updatePwdByCaptchaApi
} from '@/api/modules/profile/personalCenter'; } from '@/api/modules/profile/personalCenter';
import { useButtonLoading } from '@/composables/useLoading'; import { useButtonLoading, usePageLoading } from '@/composables/useLoading';
const { loading, startLoading, stopLoading } = usePageLoading(true);
const userInfo = reactive<UserInfo>({ const userInfo = reactive<UserInfo>({
id: 0, id: 0,
@ -290,6 +292,7 @@ const passwordFormRules = ref<FormRules>({
// //
const fetchUserProfile = async () => { const fetchUserProfile = async () => {
startLoading();
try { try {
const res = await getUserInfoApi(); const res = await getUserInfoApi();
Object.assign(userInfo, res); Object.assign(userInfo, res);
@ -297,6 +300,8 @@ const fetchUserProfile = async () => {
} catch (error) { } catch (error) {
ElMessage.error(t('personalCenter.getUserInfoFailed')); ElMessage.error(t('personalCenter.getUserInfoFailed'));
console.error('获取用户信息失败:', error); console.error('获取用户信息失败:', error);
} finally {
stopLoading();
} }
}; };

View File

@ -1,243 +1,377 @@
<template> <template>
<div class="trade-group table_form-container"> <div class="holidays-container table_form-container">
<el-form-item label="交易节假日时间设置"> <!-- 搜索区域 -->
<el-button style="margin-left: 18px" type="primary" v-auth="'contractVariety_addHoliday'" <div class="search-container">
@click="handleAdd">添加</el-button> <el-form inline :model="searchForm">
<el-button style="margin-left: 18px" type="danger" v-auth="'contractVariety_addHoliday'" <el-form-item label="节假日名称">
@click="handleDel">删除</el-button> <el-input v-model="searchForm.name" placeholder="请输入节假日名称" clearable />
</el-form-item> </el-form-item>
<!-- 表格 --> <el-form-item>
<div class="table-box"> <el-button type="primary" @click="handleSearch" v-auth="'contractVariety_addHoliday'">搜索</el-button>
<table v-loading="loading"> <el-button @click="handleReset">重置</el-button>
<thead> </el-form-item>
<tr> </el-form>
<th>名称</th> <div class="search-right">
<th>休市时间</th> <el-button type="primary" @click="handleAdd" v-auth="'contractVariety_addHoliday'">添加节假日</el-button>
<th>休市市场</th> </div>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in dataList" :key="`${item.id}-${index}`">
<td>
<el-input v-if="item.editStatus" v-model="item.name" style="width: 80px" />
<span v-else>{{ item.name }}</span>
</td>
<td>
<el-date-picker v-if="item.editStatus" v-model="item.datePicker" type="datetimerange"
start-placeholder="休市开始时间" end-placeholder="休市结束时间" format="YYYY-MM-DD HH:mm:ss"
date-format="YYYY-MM-DD HH:mm:ss" time-format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss" />
<span v-else>{{ item.datePicker[0] }} - {{ item.datePicker[1] }}</span>
</td>
<td>
<el-select multiple placeholder="请选择休市市场" style="width: 240px" v-model="item.groupId"
v-if="item.editStatus">
<el-option v-for="item in marketList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<span>{{
(item?.groupId || [])
.map(
(item: any) =>
(marketList || []).find((item2: any) => item2.value === item)?.label || '',
)
.join(',')
}}</span>
</td>
<td>
<el-input v-if="item.editStatus" style="width: 250px" v-model="item.description" />
<span v-else>{{ item.description }}</span>
</td>
<td>
<el-button type="primary" v-if="!item.editStatus" @click="item.editStatus = true"
v-auth="'contractVariety_addHoliday'">编辑</el-button>
<el-button type="danger" v-if="!item.editStatus" @click="handleDel(item.id, index)"
v-auth="'contractVariety_addHoliday'">删除</el-button>
<el-button type="primary" v-if="item.editStatus" @click="submitItem(index)"
v-auth="'contractVariety_addHoliday'">确认</el-button>
<el-button type="danger" v-if="item.editStatus" @click="item.editStatus = false"
v-auth="'contractVariety_addHoliday'">取消</el-button>
</td>
</tr>
</tbody>
</table>
</div> </div>
<!-- 列表区域使用公共 Table 组件 -->
<Table
ref="tableRef"
:table-data="tableData"
:columns="tableColumns"
row-key="id"
:loading="loading"
>
<!-- 操作列插槽 -->
<template #action="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)" v-auth="'contractVariety_addHoliday'">编辑</el-button>
<el-button type="danger" size="small" @click="handleDel(row)" v-auth="'contractVariety_addHoliday'">删除</el-button>
</template>
</Table>
<!-- 新增/编辑弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
:destroy-on-close="true"
@close="handleDialogClose"
>
<Form
ref="formRef"
:form-data="formData"
:form-rules="formRules"
:form-items="formItems"
:options-map="optionsMap"
>
<!-- 休市市场多选插槽 -->
<template #marketSelect>
<el-select
v-model="formData.groupId"
multiple
placeholder="请选择休市市场"
style="width: 100%"
clearable
>
<el-option
v-for="market in marketList"
:key="market.value"
:label="market.label"
:value="market.value"
/>
</el-select>
</template>
</Form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { getGroupListApi } from '@/api/modules/trade/group'
import { getHolidayList, addHoliday, editHoliday, delHoliday } from '@/api/modules/trade/time'
import { ElMessage, ElMessageBox } from 'element-plus'
import { usePageLoading } from '@/composables/useLoading'
import Table from '@/components/common/Table.vue'
import Form from '@/components/common/Form.vue'
defineOptions({ defineOptions({
name: 'Holidays' name: 'Holidays'
}) })
import { ref, onMounted } from 'vue'
import { getGroupListApi } from '@/api/modules/trade/group' //
import { getHolidayList, addHoliday, editHoliday, delHoliday } from '@/api/modules/trade/time' interface HolidayItem {
import { ElMessageBox } from 'element-plus' id: string
import { usePageLoading } from '@/composables/useLoading' name: string
start_time: string
end_time: string
group_id: string[]
description: string
}
interface TableRow {
id: string
name: string
startTime: string
endTime: string
marketNames: string
groupId: string[]
description: string
}
// loading // loading
const { loading, startLoading, stopLoading } = usePageLoading() const { loading, startLoading, stopLoading } = usePageLoading()
const submitLoading = ref(false)
type DataItem = { //
id?: number | string const marketList = ref<{ label: string; value: string }[]>([])
name: string
start_time?: string //
end_time?: string const tableColumns = [
datePicker: string[] { prop: 'name', label: '节假日名称', width: 150 },
groupId: number[] { prop: 'startTime', label: '休市开始时间', width: 180 },
description: string { prop: 'endTime', label: '休市结束时间', width: 180 },
editStatus: boolean { prop: 'marketNames', label: '休市市场', width: 200 },
{ prop: 'description', label: '备注' },
{ prop: 'action', label: '操作', width: 180, slotName: 'action' },
]
//
const tableData = ref<TableRow[]>([])
const tableRef = ref()
//
const searchForm = reactive({
name: '',
})
//
const dialogVisible = ref(false)
const dialogTitle = computed(() => (isEdit.value ? '编辑节假日' : '添加节假日'))
const isEdit = ref(false)
const currentId = ref<string | null>(null)
//
const formData = reactive({
name: '',
datePicker: [] as string[],
groupId: [] as string[],
description: '',
})
//
const formRules = {
name: [{ required: true, message: '请输入节假日名称', trigger: 'blur' }],
datePicker: [{ required: true, message: '请选择休市时间', trigger: 'change' }],
groupId: [{ required: true, message: '请选择休市市场', trigger: 'blur' }],
description: [{ required: true, message: '请输入备注', trigger: 'change' }],
} }
const dataList = ref<DataItem[]>([]) //
const formItems: Array<{
type: 'input' | 'date' | 'slot' | 'textarea'
label: string
prop: string
placeholder?: string
required?: boolean
dateType?: 'datetimerange'
rangeSeparator?: string
startPlaceholder?: string
endPlaceholder?: string
valueFormat?: string
slotName?: string
}> = [
{ type: 'input', label: '节假日名称', prop: 'name', placeholder: '请输入节假日名称', required: true },
{
type: 'date',
label: '休市时间',
prop: 'datePicker',
dateType: 'datetimerange',
rangeSeparator: '至',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
required: true,
},
{ type: 'slot', label: '休市市场', prop: 'groupId', slotName: 'marketSelect' },
{ type: 'textarea', label: '备注', prop: 'description', placeholder: '请输入备注' },
]
const marketList = ref<{ label: string; value: number | string }[]>([]) // Map slot 使
const optionsMap = computed(() => ({}))
//
const formRef = ref()
/**
* @description: 获取市场列表
*/
const getMarketList = async () => { const getMarketList = async () => {
getGroupListApi().then((res) => { const res = await getGroupListApi()
console.log(res) console.log('res:', res);
marketList.value = res.map((item: { name: string; id: number }[]) => ({ marketList.value = res.map((item: { name: string; id: number }) => ({
label: item.name, label: item.name,
value: item.id + '', value: String(item.id),
})) }))
}) console.log('marketList.value:', marketList.value);
}
const initDate = () => {
startLoading()
try {
getHolidayList().then((res) => {
dataList.value = res.map((item: any) => ({
id: item.id + '',
name: item.name,
start_time: item.start_time,
end_time: item.end_time,
datePicker: [item.start_time, item.end_time],
groupId: item.group_id,
description: item.description,
editStatus: false,
}))
})
} finally { stopLoading() }
}
const handleAdd = () => {
dataList.value.unshift({
name: '',
start_time: '',
end_time: '',
datePicker: [],
groupId: [],
description: '',
editStatus: true,
})
}
const submitItem = (idx: number) => {
const submitObj = {
name: dataList.value[idx]!.name,
start_time: dataList.value[idx]!.datePicker[0],
end_time: dataList.value[idx]!.datePicker[1],
group_id: dataList.value[idx]!.groupId.join(','),
description: dataList.value[idx]!.description,
}
if (dataList.value[idx]!.id) {
editHoliday({ id: dataList.value[idx]!.id, ...submitObj }).then(() => {
initDate()
})
} else {
addHoliday(submitObj).then(() => {
initDate()
})
}
}
const handleDel = (id: number | string, idx: number) => {
if (!id) {
dataList.value.splice(idx, 1)
return
}
ElMessageBox.confirm('确认删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(() => {
delHoliday({ id }).then(() => {
initDate()
})
})
} }
/** /**
* @description: 页面加载完成需要请求的数据 * @description: 获取节假日列表
*/ */
onMounted(() => { const getList = async () => {
getMarketList() startLoading()
initDate() try {
const res = await getHolidayList()
//
const filteredData = res.filter((item: HolidayItem) => {
if (searchForm.name && !item.name.includes(searchForm.name)) {
return false
}
return true
})
tableData.value = filteredData.map((item: HolidayItem) => {
const groupIds = item.group_id ? String(item.group_id).split(',').filter(Boolean) : []
const marketNames = groupIds
.map((id) => marketList.value.find((m) => m.value === id)?.label || '')
.filter(Boolean)
.join(',')
return {
id: String(item.id),
name: item.name,
startTime: item.start_time,
endTime: item.end_time,
marketNames,
groupId: groupIds,
description: item.description,
}
})
} finally {
stopLoading()
}
}
/**
* @description: 搜索
*/
const handleSearch = async () => {
await getList()
}
/**
* @description: 重置
*/
const handleReset = () => {
searchForm.name = ''
getList()
}
/**
* @description: 打开添加弹窗
*/
const handleAdd = () => {
isEdit.value = false
currentId.value = null
resetForm()
dialogVisible.value = true
}
/**
* @description: 打开编辑弹窗
*/
const handleEdit = (row: TableRow) => {
isEdit.value = true
currentId.value = row.id
formData.name = row.name
formData.datePicker = [row.startTime, row.endTime]
// 使 groupId
formData.groupId = row.groupId
formData.description = row.description
dialogVisible.value = true
}
/**
* @description: 删除
*/
const handleDel = async (row: TableRow) => {
try {
await ElMessageBox.confirm('确认删除该节假日吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
await delHoliday({ id: row.id })
ElMessage.success('删除成功')
await getList()
} catch {
//
}
}
/**
* @description: 重置表单
*/
const resetForm = () => {
formData.name = ''
formData.datePicker = []
formData.groupId = []
formData.description = ''
}
/**
* @description: 弹窗关闭
*/
const handleDialogClose = () => {
resetForm()
formRef.value?.resetFields()
}
/**
* @description: 提交表单
*/
const handleSubmit = async () => {
try {
await formRef.value?.validate()
} catch {
return
}
submitLoading.value = true
try {
const submitData = {
name: formData.name,
start_time: formData.datePicker[0],
end_time: formData.datePicker[1],
group_id: formData.groupId.join(','),
description: formData.description,
}
if (isEdit.value && currentId.value) {
await editHoliday({ id: currentId.value, ...submitData })
ElMessage.success('编辑成功')
} else {
await addHoliday(submitData)
ElMessage.success('添加成功')
}
dialogVisible.value = false
await getList()
} finally {
submitLoading.value = false
}
}
/**
* @description: 页面初始化
*/
onMounted(async () => {
await getMarketList()
await getList()
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.trade-group { .holidays-container {
overflow-y: scroll; :deep(.search-container) {
} display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 16px;
/* 给 table 及所有 td 加边框 */ .el-form {
table { display: flex;
border-collapse: collapse; flex-wrap: wrap;
width: 100%; gap: 12px;
table-layout: auto;
/* 宽度自适应内容 */
th {
font-weight: bold;
}
td,
th {
height: 48px;
border: 1px solid #dcdfe6;
padding: 4px 8px;
/* 内边距缩小 */
text-align: center;
font-size: 12px;
/* 文字变小 */
white-space: nowrap;
/* 文字不换行 */
vertical-align: middle;
/* 垂直居中 */
/* 时间选择器容器:两个选择器横排不换行 */
:deep(.el-time-picker) {
display: inline-block;
.el-range-editor {
width: 100px;
/* 缩小时间选择器宽度 */
font-size: 12px;
}
.el-range-input {
font-size: 12px;
}
} }
/* input 输入框也缩小 */ .search-right {
:deep(.el-input) { flex-shrink: 0;
width: 60px;
.el-input__inner {
font-size: 12px;
height: 28px;
}
}
/* 按钮缩小 */
:deep(.el-button) {
padding: 5px 10px;
font-size: 12px;
} }
} }
} }

View File

@ -148,7 +148,7 @@ const activeTab = ref<string>('email')
// //
const registerFormRef = ref<FormInstance>() const registerFormRef = ref<FormInstance>()
const registerForm = ref({ const registerForm = ref<any>({
email: '', email: '',
countryCode: '+86', countryCode: '+86',
phone: '', phone: '',