| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- <template>
- <div class="table-box button-table">
- <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
- <template #tableHeader="scope">
- <div class="action-buttons">
- <el-button :icon="Plus" class="button" type="primary" @click="newActivity" v-if="type"> 新建活动 </el-button>
- </div>
- </template>
- <!-- 表格操作 -->
- <template #operation="scope">
- <!-- 上架按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.上架)"
- link
- type="primary"
- @click="changeStatus(scope.row, 1)"
- >
- 上架
- </el-button>
- <!-- 下架按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.下架)"
- link
- type="primary"
- @click="changeStatus(scope.row, 6)"
- >
- 下架
- </el-button>
- <!-- 编辑按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.编辑)"
- link
- type="primary"
- @click="editRow(scope.row)"
- >
- 编辑
- </el-button>
- <!-- 查看详情按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情)"
- link
- type="primary"
- @click="toDetail(scope.row)"
- >
- 查看详情
- </el-button>
- <!-- 上传活动结果按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.上传活动结果) && !hasUploadedResult(scope.row)"
- link
- type="primary"
- @click="openUploadResultDrawer(scope.row)"
- >
- 上传活动结果
- </el-button>
- <!-- 删除按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.删除)"
- link
- type="primary"
- @click="deleteRow(scope.row)"
- >
- 删除
- </el-button>
- <!-- 查看拒绝原因按钮 -->
- <el-button
- v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看拒绝原因)"
- link
- type="primary"
- @click="viewRejectReason(scope.row)"
- >
- 查看拒绝原因
- </el-button>
- </template>
- </ProTable>
- <!-- 查看拒绝原因弹窗 -->
- <el-dialog v-model="rejectReasonDialogVisible" title="查看拒绝原因" width="600px">
- <div class="reject-reason-content">
- <div class="reject-reason-item">
- <div class="reject-reason-label">活动名称:</div>
- <div class="reject-reason-value">
- {{ rejectReasonData.name || "--" }}
- </div>
- </div>
- <div class="reject-reason-item">
- <div class="reject-reason-label">拒绝原因:</div>
- <div class="reject-reason-value reject-reason-text">
- {{ rejectReasonData.approvalComments || "暂无拒绝原因" }}
- </div>
- </div>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="closeRejectReasonDialog"> 确定 </el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 上传活动结果抽屉 -->
- <el-drawer v-model="uploadResultDrawerVisible" title="活动结果" :size="500" direction="rtl">
- <div class="upload-result-content">
- <!-- 活动成果类型选择 -->
- <div class="result-type-section">
- <div class="result-type-label">活动成果</div>
- <el-radio-group v-model="uploadResultForm.resultType">
- <el-radio :label="0"> 文字 </el-radio>
- <el-radio :label="1"> 图片 </el-radio>
- </el-radio-group>
- </div>
- <!-- 文字输入 -->
- <div v-if="uploadResultForm.resultType === 0" class="result-content-section">
- <div class="result-content-label">文字</div>
- <el-input
- v-model="uploadResultForm.resultText"
- type="textarea"
- :rows="8"
- placeholder="请输入"
- maxlength="1000"
- show-word-limit
- />
- </div>
- <!-- 图片上传 -->
- <div v-if="uploadResultForm.resultType === 1" class="result-content-section">
- <div class="result-content-label">图片</div>
- <div class="upload-area-wrapper" :class="{ 'has-file': resultImageFileList.length >= 1 }">
- <el-upload
- v-model:file-list="resultImageFileList"
- :accept="'.jpg,.jpeg,.png'"
- :auto-upload="false"
- :limit="1"
- :on-change="handleResultImageChange"
- :on-exceed="handleResultImageExceed"
- :on-preview="handleResultImagePreview"
- :on-remove="handleResultImageRemove"
- :before-remove="handleResultImageBeforeRemove"
- list-type="picture-card"
- >
- <el-icon>
- <Plus />
- </el-icon>
- </el-upload>
- </div>
- </div>
- </div>
- <template #footer>
- <div class="drawer-footer">
- <el-button @click="closeUploadResultDrawer"> 取消 </el-button>
- <el-button type="primary" @click="submitUploadResult" :loading="uploadResultSubmitting"> 确定 </el-button>
- </div>
- </template>
- </el-drawer>
- <!-- 图片预览 -->
- <el-image-viewer
- v-if="resultImageViewerVisible"
- :url-list="resultImageViewerUrlList"
- :initial-index="resultImageViewerInitialIndex"
- @close="resultImageViewerVisible = false"
- />
- </div>
- </template>
- <script setup lang="tsx" name="activityList">
- import { reactive, ref, onMounted, computed } from "vue";
- import { useRouter } from "vue-router";
- import { ElMessage, ElMessageBox } from "element-plus";
- import { Plus } from "@element-plus/icons-vue";
- import type { UploadFile, UploadFiles, UploadProps } from "element-plus";
- import ProTable from "@/components/ProTable/index.vue";
- import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
- import { getActivityList, deleteActivity, updateActivityStatus, uploadActivityResult } from "@/api/modules/operationManagement";
- import { uploadImg } from "@/api/modules/upload";
- import { localGet, usePermission } from "@/utils";
- const router = useRouter();
- const proTable = ref<ProTableInstance>();
- const type = ref(false);
- // 查看拒绝原因弹窗相关
- const rejectReasonDialogVisible = ref(false);
- const rejectReasonData = ref<any>({
- name: "",
- approvalComments: ""
- });
- // 上传活动结果抽屉相关
- const uploadResultDrawerVisible = ref(false);
- const uploadResultSubmitting = ref(false);
- const currentActivityId = ref<number>(0);
- const uploadResultForm = ref({
- resultType: 0, // 0-文字,1-图片
- resultText: "",
- resultMediaUrl: ""
- });
- const resultImageFileList = ref<UploadFile[]>([]);
- const resultImageViewerVisible = ref(false);
- const resultImageViewerUrlList = ref<string[]>([]);
- const resultImageViewerInitialIndex = ref(0);
- // 活动状态枚举
- const ACTIVITY_STATUS = {
- 待审核: 1,
- 未开始: 2,
- 审核驳回: 3,
- 进行中: 5,
- 已下架: 6,
- 已结束: 7,
- 审核通过: 8
- } as const;
- // 活动状态选项(用于搜索)
- const statusEnum = [
- { label: "待审核", value: "1" },
- { label: "未开始", value: "2" },
- { label: "审核驳回", value: "3" },
- { label: "进行中", value: "5" },
- { label: "已下架", value: "6" },
- { label: "已结束", value: "7" },
- { label: "审核通过", value: "8" }
- ];
- // 活动类型选项(用于搜索)
- const activityTypeEnum = [
- { label: "评论有礼", value: 2 },
- { label: "营销活动", value: 1 }
- ];
- // 操作按钮权限配置(根据状态-操作映射表)
- const OPERATION_PERMISSIONS = {
- 查看详情: [
- ACTIVITY_STATUS.待审核,
- ACTIVITY_STATUS.未开始,
- ACTIVITY_STATUS.进行中,
- ACTIVITY_STATUS.已下架,
- ACTIVITY_STATUS.已结束,
- ACTIVITY_STATUS.审核驳回
- ],
- 上架: [ACTIVITY_STATUS.已下架],
- 下架: [ACTIVITY_STATUS.进行中],
- 编辑: [ACTIVITY_STATUS.未开始, ACTIVITY_STATUS.审核驳回, ACTIVITY_STATUS.已下架, ACTIVITY_STATUS.已结束],
- 删除: [ACTIVITY_STATUS.未开始, ACTIVITY_STATUS.已下架, ACTIVITY_STATUS.已结束],
- 查看拒绝原因: [ACTIVITY_STATUS.审核驳回],
- 上传活动结果: [ACTIVITY_STATUS.已结束]
- } as const;
- // 判断按钮是否显示的工具函数
- const canShowButton = (status: number, allowedStatuses: readonly number[]) => {
- return allowedStatuses.includes(status);
- };
- // 判断活动是否已上传结果
- const hasUploadedResult = (row: any) => {
- // 检查是否有结果标识字段(可能的后端返回字段名:hasResult, resultUploaded, achievementUploaded, resultId等)
- // 或者检查是否有结果内容字段(resultText, resultMediaUrl等)
- return !!(
- row.hasResult ||
- row.resultUploaded ||
- row.achievementUploaded ||
- row.resultId ||
- row.resultText ||
- row.resultMediaUrl ||
- row.achievementDesc ||
- row.mediaUrls
- );
- };
- // 获取状态标签
- const getStatusLabel = (status: number) => {
- const statusMap: Record<number, string> = {
- 1: "待审核",
- 2: "未开始",
- 3: "审核驳回",
- 5: "进行中",
- 6: "已下架",
- 7: "已结束",
- 8: "审核通过"
- };
- return statusMap[status] || "--";
- };
- // 表格列配置
- const columns = reactive<ColumnProps<any>[]>([
- {
- prop: "activityType",
- label: "活动类型",
- width: 120,
- isShow: false,
- enum: activityTypeEnum,
- fieldNames: { label: "label", value: "value" },
- search: {
- el: "select",
- props: { placeholder: "请选择所属活动" },
- order: 1
- },
- render: (scope: any) => {
- const type = scope.row.activityType;
- const typeItem = activityTypeEnum.find(item => item.value === type);
- return typeItem ? typeItem.label : "-";
- }
- },
- {
- prop: "activityName",
- label: "活动名称",
- search: {
- el: "input",
- props: { placeholder: "请输入" }
- }
- },
- {
- prop: "id",
- label: "活动ID"
- },
- {
- prop: "startTime",
- label: "活动开始时间",
- render: (scope: any) => {
- return scope.row.startTime?.replace(/-/g, "/") || "--";
- }
- },
- {
- prop: "endTime",
- label: "活动结束时间",
- render: (scope: any) => {
- return scope.row.endTime?.replace(/-/g, "/") || "--";
- }
- },
- {
- prop: "couponName",
- label: "优惠券名称"
- },
- {
- prop: "status",
- label: "活动状态",
- render: (scope: any) => {
- // 优先使用返回的 statusName,如果没有则使用 getStatusLabel
- return scope.row.statusName || getStatusLabel(scope.row.status);
- },
- search: {
- el: "select",
- props: { placeholder: "请选择" }
- },
- enum: statusEnum,
- fieldNames: { label: "label", value: "value" }
- },
- { prop: "operation", label: "操作", fixed: "right", width: 300 }
- ]);
- // 初始化请求参数
- const initParam = reactive({
- storeId: localGet("createdId")
- });
- // 数据回调处理
- const dataCallback = (data: any) => {
- return {
- list: data?.records || [],
- total: data?.total || 0
- };
- };
- // 获取表格列表
- const getTableList = (params: any) => {
- // 处理参数:确保 status 是 number 类型,pageNum 和 pageSize 转换为 string
- const newParams: any = {
- ...params,
- pageNum: params.pageNum ? String(params.pageNum) : undefined,
- pageSize: params.pageSize ? String(params.pageSize) : undefined,
- status: params.status !== undefined && params.status !== null && params.status !== "" ? Number(params.status) : undefined,
- storeId: params.storeId ? String(params.storeId) : undefined
- };
- // 移除 activityType 参数(如果存在),因为新接口不支持此参数
- delete newParams.activityType;
- return getActivityList(newParams);
- };
- // 新建活动
- const newActivity = () => {
- router.push(`/operationManagement/newActivity?type=add`);
- };
- // 跳转详情页
- const toDetail = (row: any) => {
- router.push(`/operationManagement/activityDetail?id=${row.id}`);
- };
- // 编辑行数据
- const editRow = (row: any) => {
- router.push(`/operationManagement/newActivity?id=${row.id}&type=edit`);
- };
- // 删除行数据
- const deleteRow = (row: any) => {
- ElMessageBox.confirm("确定要删除吗?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- })
- .then(() => {
- const params = {
- id: row.id
- };
- return deleteActivity(params);
- })
- .then(() => {
- ElMessage.success("删除成功");
- proTable.value?.getTableList();
- })
- .catch(() => {
- // 用户取消删除,不做任何操作
- });
- };
- // 修改状态(上架/下架)
- const changeStatus = async (row: any, status: number) => {
- const res = await updateActivityStatus({ id: row.id, status: status });
- if (res && res.code == 200) {
- ElMessage.success("操作成功");
- proTable.value?.getTableList();
- }
- };
- // 查看拒绝原因
- const viewRejectReason = (row: any) => {
- rejectReasonData.value = {
- name: row.activityName,
- approvalComments: row.approvalComments
- };
- rejectReasonDialogVisible.value = true;
- };
- // 关闭拒绝原因弹窗
- const closeRejectReasonDialog = () => {
- rejectReasonDialogVisible.value = false;
- };
- // 打开上传活动结果抽屉
- const openUploadResultDrawer = (row: any) => {
- currentActivityId.value = Number(row.id);
- uploadResultForm.value = {
- resultType: 0, // 默认选择文字
- resultText: "",
- resultMediaUrl: ""
- };
- resultImageFileList.value = [];
- uploadResultDrawerVisible.value = true;
- };
- // 关闭上传活动结果抽屉
- const closeUploadResultDrawer = () => {
- uploadResultDrawerVisible.value = false;
- uploadResultForm.value = {
- resultType: 0, // 默认选择文字
- resultText: "",
- resultMediaUrl: ""
- };
- resultImageFileList.value = [];
- };
- // 处理活动结果图片变更
- const handleResultImageChange: UploadProps["onChange"] = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
- if (uploadFile.raw) {
- const fileType = uploadFile.raw.type.toLowerCase();
- const fileName = uploadFile.name.toLowerCase();
- const validTypes = ["image/jpeg", "image/jpg", "image/png"];
- const validExtensions = [".jpg", ".jpeg", ".png"];
- const isValidType = validTypes.includes(fileType) || validExtensions.some(ext => fileName.endsWith(ext));
- if (!isValidType) {
- ElMessage.warning("只支持上传 JPG、JPEG 和 PNG 格式的图片");
- const index = resultImageFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
- if (index > -1) {
- resultImageFileList.value.splice(index, 1);
- }
- return;
- }
- const maxSize = 20 * 1024 * 1024; // 20MB
- if (uploadFile.raw.size > maxSize) {
- ElMessage.warning("上传图片不得超过20M");
- const index = resultImageFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
- if (index > -1) {
- resultImageFileList.value.splice(index, 1);
- }
- return;
- }
- // 自动上传图片
- try {
- uploadFile.status = "uploading";
- const formData = new FormData();
- formData.append("file", uploadFile.raw);
- const res: any = await uploadImg(formData);
- const imageUrl = (res?.data && Array.isArray(res.data) ? res.data[0] : null) || res?.data?.fileUrl || res?.fileUrl;
- if (imageUrl) {
- uploadFile.status = "success";
- uploadFile.url = imageUrl;
- uploadResultForm.value.resultMediaUrl = imageUrl;
- } else {
- throw new Error("上传失败:未获取到图片URL");
- }
- } catch (error) {
- uploadFile.status = "fail";
- ElMessage.error("图片上传失败,请重试");
- const index = resultImageFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
- if (index > -1) {
- resultImageFileList.value.splice(index, 1);
- }
- }
- }
- };
- // 处理活动结果图片超出限制
- const handleResultImageExceed: UploadProps["onExceed"] = () => {
- ElMessage.warning("最多只能上传1张图片");
- };
- // 预览活动结果图片
- const handleResultImagePreview: UploadProps["onPreview"] = (uploadFile: UploadFile) => {
- const url = uploadFile.url || uploadFile.response?.url || uploadFile.response?.data?.[0];
- if (url) {
- resultImageViewerUrlList.value = [url];
- resultImageViewerInitialIndex.value = 0;
- resultImageViewerVisible.value = true;
- }
- };
- // 删除活动结果图片前确认
- const handleResultImageBeforeRemove: UploadProps["beforeRemove"] = async () => {
- try {
- await ElMessageBox.confirm("确定要删除这张图片吗?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- });
- return true;
- } catch {
- return false;
- }
- };
- // 删除活动结果图片
- const handleResultImageRemove: UploadProps["onRemove"] = () => {
- uploadResultForm.value.resultMediaUrl = "";
- ElMessage.success("图片已删除");
- };
- // 提交上传活动结果
- const submitUploadResult = async () => {
- // 验证必填字段
- if (uploadResultForm.value.resultType === 0) {
- // 文字类型
- if (!uploadResultForm.value.resultText || uploadResultForm.value.resultText.trim() === "") {
- ElMessage.warning("请输入文字内容");
- return;
- }
- } else if (uploadResultForm.value.resultType === 1) {
- // 图片类型
- if (!uploadResultForm.value.resultMediaUrl) {
- ElMessage.warning("请上传图片");
- return;
- }
- }
- try {
- uploadResultSubmitting.value = true;
- const params: any = {
- id: currentActivityId.value, // 活动ID
- resultType: uploadResultForm.value.resultType, // 0-文字,1-图片
- resultText: uploadResultForm.value.resultType === 0 ? uploadResultForm.value.resultText : undefined,
- resultMediaUrl: uploadResultForm.value.resultType === 1 ? uploadResultForm.value.resultMediaUrl : undefined
- };
- const res: any = await uploadActivityResult(params);
- if (res.code == 200) {
- ElMessage.success("上传成功");
- closeUploadResultDrawer();
- proTable.value?.getTableList();
- } else {
- ElMessage.error(res?.msg || "上传失败");
- }
- } catch (error) {
- console.error("上传活动结果失败:", error);
- ElMessage.error("上传失败,请重试");
- } finally {
- uploadResultSubmitting.value = false;
- }
- };
- // 页面加载时触发查询
- onMounted(async () => {
- type.value = await usePermission("新建运营活动");
- proTable.value?.getTableList();
- });
- </script>
- <style lang="scss" scoped>
- .action-buttons {
- display: flex;
- flex: 0 0 auto;
- gap: 10px;
- margin-right: 20px;
- .button {
- margin-bottom: 0;
- }
- }
- .reject-reason-content {
- padding: 20px 0;
- .reject-reason-item {
- display: flex;
- margin-bottom: 20px;
- &:last-child {
- margin-bottom: 0;
- }
- .reject-reason-label {
- flex-shrink: 0;
- min-width: 100px;
- font-size: 14px;
- font-weight: 500;
- color: #606266;
- }
- .reject-reason-value {
- flex: 1;
- font-size: 14px;
- color: #303133;
- word-break: break-word;
- &.reject-reason-text {
- min-height: 80px;
- padding: 12px;
- line-height: 1.6;
- white-space: pre-wrap;
- background-color: #f5f7fa;
- border-radius: 4px;
- }
- }
- }
- }
- // 上传活动结果抽屉样式
- .upload-result-content {
- padding: 20px 0;
- .result-type-section {
- margin-bottom: 24px;
- .result-type-label {
- margin-bottom: 12px;
- font-size: 14px;
- font-weight: 500;
- color: #606266;
- }
- }
- .result-content-section {
- .result-content-label {
- margin-bottom: 12px;
- font-size: 14px;
- font-weight: 500;
- color: #606266;
- }
- .upload-area-wrapper {
- // 当有文件时,隐藏上传框
- &.has-file {
- :deep(.el-upload--picture-card) {
- display: none !important;
- }
- }
- }
- }
- }
- .drawer-footer {
- display: flex;
- gap: 12px;
- justify-content: flex-end;
- }
- </style>
|