|
|
<template> <div class="page-container"> <div class="search-container"> <el-button type="danger" @click="add">添加</el-button> </div>
<!-- 数据 --> <el-table :data="tableData" style="width: 100%; margin-top: 20px" header-cell-class-name="table-header" @sort-change="handleSortChange" :default-sort="{ prop: null, order: null }" class="table-rounded" :loading="tableLoading" > <el-table-column prop="id" label="序号" align="center" header-align="center" width="80" > <template #default="scope"> {{ (currentPage - 1) * pageSize + scope.$index + 1 }} </template> </el-table-column> <el-table-column prop="prize_type" label="类型" align="center" header-align="center" > <template #default="scope"> {{ prizeTypeOptions.find(item => item.value === scope.row.prize_type)?.label }} </template> </el-table-column> <el-table-column prop="prize_name" label="物品名称" align="center" header-align="center" /> <el-table-column prop="stick_type" label="福签" align="center" header-align="center" > <template #default="scope"> {{ typeOptions.find(item => item.value === scope.row.stick_type)?.label }} </template> </el-table-column> <el-table-column prop="probability" label="概率" align="center" header-align="center" > <template #default="scope"> {{ scope.row.probability }}% </template> </el-table-column> <el-table-column label="状态" prop="status" align="center" header-align="center"> <template #default="scope"> <el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0" inline-prompt style=" --el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949; " active-text="ON" inactive-text="OFF" :before-change="() => beforeChangeState(scope.row)" > </el-switch> </template> </el-table-column> <el-table-column prop="time" label="时间" align="center" header-align="center" /> <el-table-column label="操作" align="center" header-align="center"> <template #default="scope"> <el-button type="text" @click="deleteDraw(scope.row)">删除</el-button> <el-button type="text" @click="handleEdit(scope.row)">编辑</el-button> </template> </el-table-column> </el-table>
<!-- 分页组件 --> <div class="demo-pagination-block"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="datatotal" /> </div> <el-dialog v-model="dialogFormVisible" width="500" :show-close="false"> <el-form :model="form" style="width: 400px; margin: 0 auto" :rules="rules" ref="formRef" > <el-form-item label="类型" prop="prize_type"> <el-select v-model="form.prize_type" placeholder="请选择类型" clearable> <el-option v-for="item in prizeTypeOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item :label="nameConfig.label" :prop="nameConfig.prop"> <el-select v-if="form.prize_type === 5" v-model="form.templateName" placeholder="请选择模板" clearable :loading="isRegionLoading" > <el-option v-for="item in templateList" :key="item.id" :label="item.name" :value="item.id" /> </el-select> <el-input v-else v-model="form[nameConfig.prop]" :type="nameConfig.type" autocomplete="off" :placeholder="nameConfig.placeholder" clearable /> </el-form-item> <el-form-item v-if="form.prize_type === 5" label="期限" prop="term_value"> <el-input v-model.number="form.term_value" placeholder="请输入模板期限" clearable > <template #append> <el-select v-model="form.time_unit" placeholder="单位" style="width: 60px" > <el-option label="日" :value=3 /> <el-option label="月" :value=2 /> <el-option label="年" :value=1 /> </el-select> </template> </el-input> </el-form-item> <el-form-item label="概率" prop="probability"> <el-input v-model.number="form.probability" type="number" autocomplete="off" placeholder="请输入概率" clearable > <template #append><span style="width: 20px;">%</span></template> </el-input> <div class="tip">(小于等于100%)</div> </el-form-item> <el-form-item label="福签" prop="stick_type"> <el-select v-model="form.stick_type" placeholder="请选择类型" clearable > <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item label="图片" prop="img"> <el-upload ref="uploadRef" v-model:file-list="fileList" class="avatar-uploader" :action="uploadUrl" :limit="1" list-type="picture-card" :on-success="handleSuccess" :before-upload="beforeUpload" :on-remove="handleRemove" :on-exceed="handleExceed" > <el-icon><Plus /></el-icon> </el-upload> <div class="tip"> 大小180*180像素,支持PNG、JPG格式,图片需小于100K </div> </el-form-item> <el-form-item label="必中用户" prop="dccodes"> <el-input v-model="form.dccodes" type="textarea" :rows="4" placeholder="请输入精网号或以Excel的格式粘贴精网号 示例: 90042088 90023488 90046788" clearable /> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button @click="dialogFormVisible = false">取消</el-button> <el-button type="danger" @click="submitForm(formRef)"> 提交 </el-button> </div> </template> </el-dialog> </div></template>
<script setup>import { ref, reactive, onMounted, computed, watch, nextTick } from "vue";import { ElMessage, genFileId, ElMessageBox } from "element-plus";import router from "../../router";import { getContentListApi, addDrawConfigApi, deleteDrawApi, changeStatusApi, getTemplateApi,} from "../../api/eventManagement";const uploadUrl = import.meta.env.VITE_API_BASE_URLXXCG + "hljw/api/aws/upload";const uploadRef = ref();const fileList = ref([]);const formRef = ref();const isEdit = ref(false); // 是否为编辑状态
const form = reactive({ id: undefined, stick_type: "", prize_type: "", item_name: "", num: null, probability: null, img: "", templateName: "", term_value: null, time_unit: 3, dccodes: "",});const isRegionLoading = ref(false);const templateList = ref([]);const typeOptions = ref([ { label: "好运签", value: 1 }, { label: "福气签", value: 2 }, { label: "富贵签", value: 3 }, { label: "财神签", value: 4 }, { label: "上上签", value: 5 }, { label: "锦鲤签", value: 6 },]);const prizeTypeOptions = ref([ { label: "金币", value: 2 }, { label: "金豆", value: 3 }, { label: "Token", value: 1 }, { label: "实物", value: 4 }, { label: "模板", value: 5 },]);const nameConfig = computed(() => { switch (form.prize_type) { case 1: // Token
return { label: "数量", placeholder: "请输入Token数量", prop: "num", type: "number" }; case 2: // 金币
return { label: "数量", placeholder: "请输入金币数量", prop: "num", type: "number" }; case 3: // 金豆
return { label: "数量", placeholder: "请输入金豆数量", prop: "num", type: "number" }; case 5: // 模板
return { label: "名称", placeholder: "请输入模板名称", prop: "templateName", type: "text" }; default: // 默认情况(未选择或实物)
return { label: "名称", placeholder: "请输入物品名称", prop: "item_name", type: "text" }; }});const handleSuccess = (response, uploadFile) => { form.img = response.data.url;};
const beforeUpload = (rawFile) => { if (!rawFile.type.startsWith("image/")) { ElMessage.error("请上传图片文件!"); return false; } else if (rawFile.size / 1024 > 100) { ElMessage.error("图片大小必须小于100K!"); return false; } return true;};
const handleRemove = (file, fileList) => { form.img = "";};
const handleExceed = (files) => { // 1. 清空当前文件列表
uploadRef.value.clearFiles(); const file = files[0]; // 2. 必须生成新的 uid,否则可能导致 key 冲突无法上传
file.uid = genFileId(); // 3. 手动选择文件
uploadRef.value.handleStart(file); // 4. 手动触发上传
uploadRef.value.submit();};
// token
const token = localStorage.getItem("token");
const dialogFormVisible = ref(false);
const rules = computed(() => { const baseRules = { stick_type: [{ required: true, message: "请选择类型", trigger: "change" }], prize_type: [{ required: true, message: "请选择类型", trigger: "change" }], probability: [ { required: true, message: "请输入概率", trigger: "blur" }, // 为负数时提示
{ validator: validateNum, trigger: "blur" }, ], img: [{ required: true, message: "请上传图片", trigger: "change" }], // 上传通常用 change
}; if ([1, 2, 3].includes(form.prize_type)) { return { ...baseRules, num: [ { required: true, message: "请输入数量", trigger: "blur" }, // 为负数时提示
{ validator: validateNum, trigger: "blur" }, ], }; } else if (form.prize_type === 5) { return { ...baseRules, templateName: [ { required: true, message: "请选择模板", trigger: "change" }, ], term_value: [ { required: true, message: "请输入期限", trigger: "blur" }, // 非正整数时提示
{ validator: validateTerm, trigger: "blur" }, ], }; } else { return { ...baseRules, item_name: [ { required: true, message: "请输入物品名称", trigger: "blur" }, ], }; }});
watch( () => form.prize_type, (newVal) => { if (isEdit.value || !newVal) { return; } // 切换类型时,清除之前的输入值,避免校验报错或数据混乱
form.item_name = ""; form.templateName = ""; form.num = null; form.term_value = null; form.time_unit = 3; // 也可以清除校验状态
if (formRef.value) { formRef.value.clearValidate(["item_name", "templateName", "num"]); } });
const validateNum = (rule, value, callback) => { // 如果值为空,交给 required 规则处理
if (value === "" || value === null || value === undefined) { callback(); return; } // 转换为数字进行判断
if (Number(value) < 0) { callback(new Error("不能为负数")); } else { callback(); }};
const validateTerm = (rule, value, callback) => { // 如果值为空,交给 required 规则处理
if (value === "" || value === null || value === undefined) { callback(); return; } // 转换为数字进行判断
if (!Number.isInteger(Number(value)) || Number(value) <= 0) { return callback(new Error("必须为正整数")); } else { callback(); }};
// 表格数据
const tableData = ref([]);const tableLoading = ref(false);const datatotal = ref(0);
// 分页参数
const currentPage = ref(1);const pageSize = ref(10);
const beforeChangeState = (row) => { return new Promise(async (resolve, reject) => { try { const targetStatus = row.status === 1 ? 0 : 1; await changeStatusApi({ token: token, id: row.id, status: targetStatus, }); ElMessage.success("状态更新成功"); resolve(true); // fetchTableData();
} catch (error) { // reject()拒绝操作,switch 组件会自动回滚状态
reject(new Error("状态更新失败")); } });};
// 重置表单数据
const resetForm = () => { form.id = undefined; form.stick_type = ""; form.prize_type = ""; form.item_name = ""; form.templateName = ""; form.term_value = null; form.time_unit = 3; form.num = null; form.probability = null; form.img = ""; form.dccodes = ""; fileList.value = [];};
// 添加按钮
const add = () => { isEdit.value = false; resetForm(); dialogFormVisible.value = true; // 清除校验红字
/* nextTick虽然是异步的,但它执行的时机非常“快”(微任务队列)。设置 dialogFormVisible = true 时,<el-dialog> 开始渲染。 但由于弹窗可能有动画,或者内部组件是懒加载的,在 nextTick 执行的那一瞬间,<el-form> 可能还没有真正渲染到 DOM 上。 此时 formRef.value 是 undefined,所以 clearValidate() 其实根本没有执行成功。*/ // nextTick(() => {
// formRef.value?.clearValidate();
// });
//setTimeout 会将任务推到宏任务队列,确保在 DOM 渲染、弹窗打开动画开始之后再执行,保证 formRef 一定存在。
setTimeout(() => { if (formRef.value) { formRef.value.clearValidate(); } }, 0);};
const submitForm = async () => { try { await formRef.value.validate(); const requestParams = { token: token, stick_type: form.stick_type, prize_type: form.prize_type, probability: form.probability, img: form.img, dccodes: form.dccodes, }; if (form.id) { requestParams.id = form.id; } if ([1, 2, 3].includes(form.prize_type)) { requestParams.num = Number(form.num); } else if (form.prize_type === 5) { requestParams.indicator_id = form.templateName; requestParams.num = Number(form.term_value); requestParams.time_unit = form.time_unit; const selectedItem = templateList.value.find(item => item.id === form.templateName); requestParams.item_name = selectedItem ? selectedItem.name : ''; } else { requestParams.item_name = form.item_name; } const data = await addDrawConfigApi(requestParams); ElMessage.success(form.id ? "修改成功" : "添加成功"); dialogFormVisible.value = false; fetchTableData(); } catch (error) {}};
const deleteDraw = async (row) => { try { await ElMessageBox.confirm("确定要删除吗?", "确认删除", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", confirmButtonClass: "custom-confirm-btn", }); const requestParams = { token: token, id: row.id, }; const data = await deleteDrawApi(requestParams); ElMessage.success("删除成功"); fetchTableData(); } catch (error) { if (error === "cancel") { // 用户点击取消
ElMessage.info("已取消删除"); } else { // 删除操作失败
ElMessage.error("删除失败"); } }};
const handleEdit = (row) => { isEdit.value = true;
resetForm();
form.id = row.id; form.stick_type = row.stick_type; form.prize_type = row.prize_type; // 触发 watch,可能会清空字段,所以需要在nextTick后赋值
form.probability = row.probability; form.img = row.img; form.dccodes = row.dccodes || "";
// 图片回显 (让 Upload 组件显示图片)
if (row.img) { fileList.value = [{ name: 'img', url: row.img }]; }
// 根据类型回显不同字段
if ([1, 2, 3].includes(row.prize_type)) { form.num = row.num; } else if (row.prize_type === 5) { // 模板类型:回显模板ID和期限
form.templateName = row.indicator_id; form.term_value = row.num; form.time_unit = row.time_unit; } else { // 实物类型
form.item_name = row.item_name; }
dialogFormVisible.value = true;
// 6. 在 DOM 更新后,解除锁并清除校验
nextTick(() => { isEdit.value = false; // 解除锁
if (formRef.value) { formRef.value.clearValidate(); // 去掉一打开就红一片的校验信息
} });};
// 获取模板列表
const fetchTemplateList = async () => { try { isRegionLoading.value = true; const data = await getTemplateApi({ token: token, app_form: "en", }); templateList.value = data.list; } catch (error) { console.error("获取模板列表失败:", error); templateList.value = []; } finally { isRegionLoading.value = false; }};
// 获取表格数据
const fetchTableData = async () => { try { tableLoading.value = true; const requestParams = { token: token, page: currentPage.value, page_size: pageSize.value, }; const data = await getContentListApi(requestParams); tableData.value = data.list; datatotal.value = data.total; } catch (error) { console.error("获取表格数据失败:", error); tableData.value = []; datatotal.value = 0; } finally { tableLoading.value = false; }};
// 组件挂载时:获取地区列表 + 初始化表格数据
onMounted(() => { fetchTemplateList(); fetchTableData();});
// 分页方法
const handleSizeChange = (val) => { pageSize.value = val; fetchTableData(); console.log(`每页 ${val} 条`);};
const handleCurrentChange = (val) => { currentPage.value = val; fetchTableData(); console.log(`当前页: ${val}`);};</script>
<style scoped>/* 父容器 */.page-container { position: relative; min-height: 600px;}
/* 搜索区域 */.search-container { display: flex; height: auto; flex-direction: column; justify-content: center; align-items: flex-start; gap: 12px; align-self: stretch; border-radius: 8px; background: #fefaf9; box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25); padding: 15px; margin-bottom: 20px;}
/* 表格样式 */.table-rounded { border-radius: 12px !important; overflow: hidden !important; border: 1px solid #e4e7ed !important; height: 750px;}
.table-header { text-align: center !important; font-weight: 800 !important; font-size: 15px !important; color: #333 !important; background-color: #f8f9fa !important;}
.el-table__cell { border-right: none !important; border-bottom: 1px solid #e4e7ed !important;}
.el-table__header th.el-table__cell { border-right: none !important; border-bottom: 1px solid #e4e7ed !important;}
.el-table__row:hover .el-table__cell { background-color: #fafafa !important;}
/* 分页组件样式 */.demo-pagination-block { display: flex; width: 100%; height: 44px; padding: 0 16px; align-items: center; gap: 16px; position: absolute; margin-top: 10px; border-radius: 0 0 3px 3px; border-top: 1px solid #eaeaea; background: #fefbfb; box-sizing: border-box;}
.tip { font-size: 12px; color: #8c939d;}
.avatar-uploader .avatar { width: 120px; height: 120px; display: block;}</style>
<style>.custom-confirm-btn { background: #e13d52; border-color: #e13d52; color: white !important; border-radius: 6px !important; padding: 8px 16px !important;}
.custom-confirm-btn:hover { background: #d88b95; border-color: #d88b95;}
.avatar-uploader .el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast);}
.avatar-uploader .el-upload:hover { border-color: var(--el-color-primary);}
.el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 120px; height: 120px; text-align: center;}</style>
|