11 Commits

  1. 84
      src/api/eventManagement.js
  2. 3
      src/layout/Layout.vue
  3. 24
      src/router/index.js
  4. 220
      src/views/EventManagement/ContentConfiguration.vue
  5. 524
      src/views/EventManagement/ExchangeRecords.vue
  6. 385
      src/views/EventManagement/LearningPageConfig.vue
  7. 3
      src/views/EventManagement/WinningRecords.vue

84
src/api/eventManagement.js

@ -1,7 +1,7 @@
import request from '../utils/myAxios';
var base_url = import.meta.env.VITE_API_BASE_URL
// 获取用户抽奖记录
// 历史记录--获取用户抽奖记录
export function userLuckyDrawListApi(params) {
return request({
url: base_url + "/admin/luckyDraw/list",
@ -10,7 +10,7 @@ export function userLuckyDrawListApi(params) {
});
}
// 导出用户抽奖记录
// 历史记录--导出用户抽奖记录
export function exportUserLuckyDrawListApi(params) {
return request({
url: base_url + "/admin/luckyDraw/export/create",
@ -19,7 +19,7 @@ export function exportUserLuckyDrawListApi(params) {
});
}
// 获取配置列表
// 内容配置--获取配置列表
export function getContentListApi(params) {
return request({
url: base_url + "/admin/luckyDraw/getContentList",
@ -28,7 +28,7 @@ export function getContentListApi(params) {
});
}
// 添加奖品
// 内容配置--添加奖品
export function addDrawConfigApi(params) {
return request({
url: base_url + "/admin/luckyDraw/addDrawConfig",
@ -37,7 +37,7 @@ export function addDrawConfigApi(params) {
});
}
// 删除奖品
// 内容配置--删除奖品
export function deleteDrawApi(params) {
return request({
url: base_url + "/admin/luckyDraw/deleteDraw",
@ -46,11 +46,83 @@ export function deleteDrawApi(params) {
});
}
// 修改奖品状态
// 内容配置--修改奖品状态
export function changeStatusApi(params) {
return request({
url: base_url + "/admin/luckyDraw/changeStatus",
method: "post",
data: params,
});
}
// 内容配置--获取模板名称
export function getTemplateApi(params) {
return request({
url: base_url + "/admin/indicator/list",
method: "post",
data: params,
});
}
// Token兑换记录--获取token兑换记录
export function getTokenExchangeRecordApi(params) {
return request({
url: base_url + "/admin/luckyDraw/tokenExchangeRecord",
method: "post",
data: params,
});
}
// Token兑换记录--导出token兑换记录
export function exportTokenExchangeRecordApi(params) {
return request({
url: base_url + "/admin/luckyDraw/tokenExport/create",
method: "post",
data: params,
});
}
// Token兑换记录--获取token兑换方式下拉框
export function getDropDownListApi(params) {
return request({
url: base_url + "/admin/luckyDraw/token/dropDownList",
method: "post",
data: params,
});
}
// 获取学习页配置列表--已接
export function getLearningPageListApi(params) {
return request({
url: base_url + "/admin/studyArticle/list",
method: "post",
data: params,
});
}
// 添加关联文章--已接
export function addLearningPageApi(params) {
return request({
url: base_url + "/admin/studyArticle/save",
method: "post",
data: params,
});
}
// 删除学习页配置--已接
export function deleteLearningPageApi(params) {
return request({
url: base_url + "/admin/studyArticle/delete",
method: "post",
data: params,
});
}
// 修改关联状态--已接
export function changeLearningPageStatusApi(params) {
return request({
url: base_url + "/admin/studyArticle/status",
method: "post",
data: params,
});
}

3
src/layout/Layout.vue

@ -312,6 +312,9 @@ onMounted(async () => {
margin-top: 169px;
width: calc(100% - 20px);
border-right: none;
height: 70%;
overflow-y: auto;
overflow-x: hidden;
}
/* 主内容区样式 */

24
src/router/index.js

@ -87,12 +87,12 @@ const routes = [
component: () => import('../views/PlatformData/UserOverview.vue'),
meta: { title: '用户数据概览', showSidebar: true }
},
{
path: 'loginStats',
name: 'loginStats',
component: () => import('../views/PlatformData/UserLoginStats.vue'),
meta: { title: '用户登录统计', showSidebar: true }
},
// {
// path: 'loginStats',
// name: 'loginStats',
// component: () => import('../views/PlatformData/UserLoginStats.vue'),
// meta: { title: '用户登录统计', showSidebar: true }
// },
{
path: 'activityStats',
name: 'activityStats',
@ -123,6 +123,18 @@ const routes = [
component: () => import('../views/EventManagement/ContentConfiguration.vue'),
meta: { title: '内容配置', showSidebar: true }
},
{
path: 'exchangeRecords',
name: 'exchangeRecords',
component: () => import('../views/EventManagement/ExchangeRecords.vue'),
meta: { title: 'Token兑换记录', showSidebar: true }
},
{
path: 'learningPageConfig',
name: 'learningPageConfig',
component: () => import('../views/EventManagement/LearningPageConfig.vue'),
meta: { title: '学习页配置', showSidebar: true }
},
]
},
]

220
src/views/EventManagement/ContentConfiguration.vue

@ -30,7 +30,11 @@
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="物品名称"
@ -42,14 +46,20 @@
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"
/>
<el-table-column label="状态" prop="status">
>
<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"
@ -76,6 +86,7 @@
<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>
@ -99,8 +110,8 @@
:rules="rules"
ref="formRef"
>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type" placeholder="请选择类型" clearable>
<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"
@ -110,7 +121,22 @@
</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"
@ -118,6 +144,25 @@
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"
@ -126,7 +171,7 @@
placeholder="请输入概率"
clearable
>
<template #append>%</template>
<template #append><span style="width: 20px;">%</span></template>
</el-input>
<div class="tip">(小于等于100%)</div>
</el-form-item>
@ -160,7 +205,7 @@
<el-icon><Plus /></el-icon>
</el-upload>
<div class="tip">
大小180*180像素支持PNGJPG格式图片需小于500K
大小180*180像素支持PNGJPG格式图片需小于100K
</div>
</el-form-item>
</el-form>
@ -177,7 +222,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, computed, watch } from "vue";
import { ref, reactive, onMounted, computed, watch, nextTick } from "vue";
import { ElMessage, genFileId, ElMessageBox } from "element-plus";
import router from "../../router";
import {
@ -185,19 +230,27 @@ import {
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: "",
type: "",
prize_type: "",
item_name: "",
num: null,
probability: null,
img: "",
templateName: "",
term_value: null,
time_unit: 3,
});
const isRegionLoading = ref(false);
const templateList = ref([]);
const typeOptions = ref([
{ label: "好运签", value: 1 },
{ label: "福气签", value: 2 },
@ -211,15 +264,18 @@ const prizeTypeOptions = ref([
{ label: "金豆", value: 3 },
{ label: "Token", value: 1 },
{ label: "实物", value: 4 },
{ label: "模板", value: 5 },
]);
const nameConfig = computed(() => {
switch (form.type) {
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" };
}
@ -232,8 +288,8 @@ const beforeUpload = (rawFile) => {
if (!rawFile.type.startsWith("image/")) {
ElMessage.error("请上传图片文件!");
return false;
} else if (rawFile.size / 1024 > 500) {
ElMessage.error("图片大小必须小于500K!");
} else if (rawFile.size / 1024 > 100) {
ElMessage.error("图片大小必须小于100K!");
return false;
}
return true;
@ -263,7 +319,7 @@ const dialogFormVisible = ref(false);
const rules = computed(() => {
const baseRules = {
stick_type: [{ required: true, message: "请选择类型", trigger: "change" }],
type: [{ required: true, message: "请选择类型", trigger: "change" }],
prize_type: [{ required: true, message: "请选择类型", trigger: "change" }],
probability: [
{ required: true, message: "请输入概率", trigger: "blur" },
//
@ -271,7 +327,7 @@ const rules = computed(() => {
],
img: [{ required: true, message: "请上传图片", trigger: "change" }], // change
};
if ([1, 2, 3].includes(form.type)) {
if ([1, 2, 3].includes(form.prize_type)) {
return {
...baseRules,
num: [
@ -280,6 +336,18 @@ const rules = computed(() => {
{ 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,
@ -291,14 +359,20 @@ const rules = computed(() => {
});
watch(
() => form.type,
() => {
() => 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", "num"]);
formRef.value.clearValidate(["item_name", "templateName", "num"]);
}
}
);
@ -317,6 +391,20 @@ const validateNum = (rule, value, 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);
@ -347,9 +435,13 @@ const beforeChangeState = (row) => {
//
const resetForm = () => {
form.id = undefined;
form.stick_type = "";
form.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 = "";
@ -358,8 +450,24 @@ const resetForm = () => {
//
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 () => {
@ -368,17 +476,26 @@ const submitForm = async () => {
const requestParams = {
token: token,
stick_type: form.stick_type,
type: form.type,
prize_type: form.prize_type,
probability: form.probability,
img: form.img,
};
if ([1, 2, 3].includes(form.type)) {
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("添加成功");
ElMessage.success(form.id ? "修改成功" : "添加成功");
dialogFormVisible.value = false;
fetchTableData();
} catch (error) {}
@ -410,6 +527,64 @@ const deleteDraw = async (row) => {
}
};
const handleEdit = (row) => {
isEdit.value = true;
resetForm();
form.id = row.id;
form.stick_type = row.stick_type;
form.prize_type = row.prize_type; // watchnextTick
form.probability = row.probability;
form.img = row.img;
// ( 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 {
@ -433,6 +608,7 @@ const fetchTableData = async () => {
// +
onMounted(() => {
fetchTemplateList();
fetchTableData();
});

524
src/views/EventManagement/ExchangeRecords.vue

@ -0,0 +1,524 @@
<!-- @format -->
<template>
<div>
<div class="page-container">
<!-- 搜索区域 -->
<div class="search-container" @keyup.enter="search">
<div class="search-group">
<div class="search-group1">
<div class="search-item">
<span class="form-label">账号</span>
<el-input
v-model="searchForm.dccode"
placeholder="请输入账号"
clearable
style="height: 36px; width: 140px"
/>
</div>
<div class="search-item">
<span class="form-label">姓名</span>
<el-input
v-model="searchForm.name"
placeholder="请输入姓名"
clearable
style="height: 36px; width: 140px"
/>
</div>
<div class="search-item">
<span class="form-label">归属</span>
<el-input
v-model="searchForm.inviter"
placeholder="请输入归属账号"
clearable
style="height: 36px; width: 140px"
/>
</div>
</div>
<div class="search-group2">
<div class="search-item">
<span class="form-label">兑换方式</span>
<el-select
v-model="searchForm.type"
placeholder="请选择兑换方式"
clearable
style="height: 36px; width: 160px"
>
<el-option
v-for="item in exchangeTypeList"
:key="item.gold_id"
:label="item.gold_type"
:value="item.gold_id"
/>
</el-select>
</div>
<div class="search-item">
<span class="form-label">兑换日期</span>
<el-date-picker
v-model="searchDate"
type="datetimerange"
:shortcuts="shortcuts"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="height: 36px; width: 280px"
/>
</div>
</div>
</div>
<div class="button-group">
<el-button type="primary" @click="search">搜索</el-button>
<el-button type="success" @click="exportExcel"
>导出Excel列表</el-button
>
<el-button color="#626aef" @click="exportList"
>查看导出列表</el-button
>
<el-button type="primary" @click="resetBn">重置</el-button>
</div>
</div>
<!-- 数据 -->
<el-table
:data="tableData"
style="width: 100%; margin-top: 20px"
header-cell-class-name="table-header"
class="table-rounded"
:loading="tableLoading"
>
<el-table-column
prop="id"
label="序号"
align="center"
header-align="center"
width="80"
>
<template #default="scope">
{{ (currentPage - 1) * pageSizeDM + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column
prop="dccode"
label="账号"
align="center"
header-align="center"
/>
<el-table-column
prop="name"
label="姓名"
align="center"
header-align="center"
/>
<el-table-column
prop="inviter"
label="归属"
align="center"
header-align="center"
/>
<el-table-column
prop="type"
label="兑换方式"
align="center"
header-align="center"
/>
<el-table-column
prop="token_num"
label="兑换数量"
align="center"
header-align="center"
>
<template #default="scope"> {{ scope.row.token_num }}Token </template>
</el-table-column>
<el-table-column
prop="created_at"
label="兑换时间"
align="center"
header-align="center"
/>
</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="pageSizeDM"
layout="total, sizes, prev, pager, next, jumper"
:total="datatotalDM"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from "vue";
import { ElMessage } from "element-plus";
import {
getTokenExchangeRecordApi,
exportTokenExchangeRecordApi,
getDropDownListApi,
} from "../../api/eventManagement";
import router from "../../router";
import { useRoute } from "vue-router";
import { usePermissionStore } from "../../store/permission";
const permissionStore = usePermissionStore();
//
const exchangeTypeList = ref([]);
// token
const token = localStorage.getItem("token");
//permission
const permission = ref("-1");
//
const route = useRoute();
// +
onMounted(async () => {
permission.value = permissionStore.permission;
fetchExchangeTypeList();
getTableData();
});
const shortcuts = [
{
text: "最近7天",
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 7);
return [start, end];
},
},
{
text: "最近1个月",
value: () => {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 1);
return [start, end];
},
},
{
text: "最近3个月",
value: () => {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 3);
return [start, end];
},
},
];
//
const searchForm = reactive({
dccode: "",
name: "",
inviter: "",
type: "",
});
const searchDate = ref([]);
//
const tableData = ref([]);
const tableLoading = ref(false);
const datatotalDM = ref(0);
//
const currentPage = ref(1);
const pageSizeDM = ref(15);
//
const fetchExchangeTypeList = async () => {
try {
const data = await getDropDownListApi({
token: token
});
exchangeTypeList.value = data;
} catch (error) {
console.error("获取兑换方式列表失败:", error);
exchangeTypeList.value = [];
}
};
//
const getTableData = async () => {
try {
tableLoading.value = true;
const requestParams = {
token: token,
dccode: searchForm.dccode,
name: searchForm.name,
inviter: searchForm.inviter,
type: Number(searchForm.type),
start_at:
searchDate.value && searchDate.value[0] ? searchDate.value[0] : "",
end_at:
searchDate.value && searchDate.value[1] ? searchDate.value[1] : "",
page: currentPage.value,
page_size: pageSizeDM.value,
};
const data = await getTokenExchangeRecordApi(requestParams);
tableData.value = data.list;
datatotalDM.value = data.total;
} catch (error) {
console.error("获取表格数据失败:", error);
tableData.value = [];
datatotalDM.value = 0;
} finally {
tableLoading.value = false;
}
};
//
const handleSizeChange = (val) => {
pageSizeDM.value = val;
getTableData();
console.log(`每页 ${val}`);
};
const handleCurrentChange = (val) => {
currentPage.value = val;
getTableData();
console.log(`当前页: ${val}`);
};
//
const search = () => {
currentPage.value = 1;
getTableData();
};
//
const resetBn = () => {
searchForm.dccode = "";
searchForm.name = "";
searchForm.inviter = "";
searchForm.type = "";
searchDate.value = [];
currentPage.value = 1;
pageSizeDM.value = 15;
getTableData();
};
// Excel
const exportExcel = async () => {
const requestParams = {
token: token,
dccode: searchForm.dccode,
name: searchForm.name,
inviter: searchForm.inviter,
type: Number(searchForm.type),
start_at:
searchDate.value && searchDate.value[0] ? searchDate.value[0] : "",
end_at:
searchDate.value && searchDate.value[1] ? searchDate.value[1] : "",
};
const data = await exportTokenExchangeRecordApi(requestParams);
if (data != "") {
ElMessage.success("已导出");
}
};
//
const exportList = () => {
router.push({
path: "/userPermissions/export",
});
};
//
const formatDate = (date) => {
if (!date) return "";
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}/${month}/${day}`;
};
//
const checkmodel = () => {
if (indicator_id.value.length === 0) {
ElMessage.error("请至少选择一个指标");
return false;
}
return true;
};
</script>
<style scoped>
/* 父容器 */
.page-container {
position: relative;
min-height: 700px;
}
/* 搜索区域 */
.search-container {
display: flex;
height: auto;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
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;
}
.search-group {
display: flex;
flex-direction: column;
flex: 1;
}
.search-group1 {
display: flex;
align-items: center;
gap: 15px;
}
.search-group2 {
display: flex;
margin-top: 15px;
align-items: center;
gap: 15px;
}
/* 单个搜索项 */
.search-item {
display: flex;
align-items: center;
gap: 6px;
}
/* 搜索标签文字 */
.form-label {
font-weight: 800 !important;
font-size: 15px;
text-align: left;
color: #333;
margin: 13px 0;
font-family: "SimHei", "Heiti SC", "Microsoft YaHei", sans-serif !important;
display: block;
}
/* 按钮组 */
.button-group {
display: flex;
align-items: center;
gap: 0px !important;
margin-left: auto;
flex-shrink: 0;
}
/* 按钮样式 */
.button-group .el-button {
padding: 6px 10px !important;
font-size: 14px !important;
height: 36px !important;
}
/* 表格样式 */
.table-rounded {
border-radius: 12px !important;
overflow: hidden !important;
border: 1px solid #e4e7ed !important;
height: 650px;
}
.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;
}
/* 添加/修改样式 */
.form-item {
margin-bottom: 24px;
padding-left: 20px;
padding-right: 20px;
text-align: left;
}
/* 文本溢出省略样式(深度探索) */
.ellipsis-text {
display: inline-block;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #ff0000 !important;
border-color: #ff0000 !important;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
border-color: #fff !important;
}
:deep(.el-checkbox__input:hover .el-checkbox__inner) {
border-color: #ff0000 !important;
}
:deep(.el-checkbox__input:focus .el-checkbox__inner) {
box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.2) !important;
}
:deep(.el-checkbox__label) {
color: #333 !important;
font-size: 14px !important;
}
.label {
color: #666;
font-weight: 500;
}
.value {
color: #333;
}
</style>

385
src/views/EventManagement/LearningPageConfig.vue

@ -0,0 +1,385 @@
<template>
<div class="page-container">
<div class="search-container">
<el-button type="danger" @click="addArticle">添加关联文章</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="article_id"
label="文章id"
align="center"
header-align="center"
/>
<el-table-column
prop="article_title"
label="关联文章"
align="center"
header-align="center"
/>
<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"
:disabled="activeRecordId && activeRecordId !== scope.row.id"
:before-change="() => beforeChangeState(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" header-align="center">
<template #default="scope">
<el-button type="text" @click="deleteArticle(scope.row)">删除</el-button>
<el-button type="text" @click="editArticle(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, 15, 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" :title="isEditMode ? '更改关联文章' : '添加关联文章'">
<el-form
:model="form"
style="width: 400px; margin: 0 auto"
:rules="rules"
ref="formRef"
>
<el-form-item label="文章id" prop="article_id">
<el-input
v-model="form.article_id"
type="number"
autocomplete="off"
placeholder="请输入文章id"
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 } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import router from "../../router";
import {
getLearningPageListApi,
addLearningPageApi,
deleteLearningPageApi,
changeLearningPageStatusApi,
} from "../../api/eventManagement";
const formRef = ref();
const form = reactive({
id: null,
article_id: null,
});
const validateNum = (rule, value, callback) => {
if (value === "" || value === null || value === undefined) {
callback();
return;
}
if (Number(value) < 0) {
callback(new Error("不能为负数"));
} else {
callback();
}
};
const rules = {
article_id: [
{ required: true, message: "请输入文章id", trigger: "blur" },
{ validator: validateNum, trigger: "blur" },
],
};
// token
const token = localStorage.getItem("token");
const dialogFormVisible = ref(false);
const isEditMode = ref(false);
//
const tableData = ref([]);
const tableLoading = ref(false);
const datatotal = ref(0);
const activeRecordId = ref(null);
//
const currentPage = ref(1);
const pageSize = ref(15);
const beforeChangeState = (row) => {
return new Promise(async (resolve, reject) => {
try {
const targetStatus = row.status === 1 ? 0 : 1;
// on
if (targetStatus === 1 && activeRecordId.value && activeRecordId.value !== row.id) {
ElMessage.error("已有状态为ON的记录,无法修改其他记录的状态");
reject(new Error("已有状态为ON的记录"));
return;
}
await changeLearningPageStatusApi({
token: token,
id: row.id,
status: targetStatus,
});
ElMessage.success("状态更新成功");
resolve(true);
fetchTableData();
} catch (error) {
reject(new Error("状态更新失败"));
}
});
};
//
const resetForm = () => {
form.id = null;
form.article_id = null;
};
//
const addArticle = () => {
resetForm();
isEditMode.value = false;
dialogFormVisible.value = true;
};
//
const editArticle = (row) => {
form.id = row.id;
form.article_id = row.article_id;
isEditMode.value = true;
dialogFormVisible.value = true;
};
const submitForm = async () => {
try {
await formRef.value.validate();
const requestParams = {
token: token,
article_id: form.article_id,
};
if (isEditMode.value) {
requestParams.id = form.id;
await addLearningPageApi(requestParams);
ElMessage.success("编辑成功");
} else {
await addLearningPageApi(requestParams);
ElMessage.success("添加成功");
}
dialogFormVisible.value = false;
fetchTableData();
} catch (error) {}
};
const deleteArticle = async (row) => {
try {
await ElMessageBox.confirm("确定要删除吗?", "确认删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
confirmButtonClass: "custom-confirm-btn",
});
const requestParams = {
token: token,
id: row.id,
};
const data = await deleteLearningPageApi(requestParams);
ElMessage.success("删除成功");
fetchTableData();
} catch (error) {
if (error === "cancel") {
ElMessage.info("已取消删除");
} else {
ElMessage.error("删除失败");
}
}
};
//
const fetchTableData = async () => {
try {
tableLoading.value = true;
const requestParams = {
token: token,
page: currentPage.value,
page_size: pageSize.value,
};
const data = await getLearningPageListApi(requestParams);
tableData.value = data.list;
datatotal.value = data.total;
// on
const activeRecord = data.list.find(item => item.status === 1);
activeRecordId.value = activeRecord ? activeRecord.id : null;
} catch (error) {
console.error("获取表格数据失败:", error);
tableData.value = [];
datatotal.value = 0;
activeRecordId.value = null;
} finally {
tableLoading.value = false;
}
};
// +
onMounted(() => {
fetchTableData();
});
//
const handleSizeChange = (val) => {
pageSize.value = val;
fetchTableData();
console.log(`每页 ${val}`);
};
const handleCurrentChange = (val) => {
currentPage.value = val;
fetchTableData();
console.log(`当前页: ${val}`);
};
const handleSortChange = (val) => {
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;
}
</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;
}
</style>

3
src/views/EventManagement/WinningRecords.vue

@ -101,7 +101,7 @@
header-align="center"
/>
<el-table-column
prop="prize_count"
prop="num"
label="数量"
align="center"
header-align="center"
@ -143,6 +143,7 @@ const typeOptions = ref([
{ label: "金豆", value: 3 },
{ label: "Token", value: 1 },
{ label: "实物", value: 4 },
{ label: "模板", value: 5 },
]);
//
const searchForm = reactive({

Loading…
Cancel
Save