newActivity.vue 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477
  1. <template>
  2. <!-- 运营活动管理 - 新增/编辑页面 -->
  3. <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
  4. <div class="header">
  5. <el-button @click="goBack"> 返回 </el-button>
  6. <h2 class="title">{{ type == "add" ? "新建" : "编辑" }}活动</h2>
  7. </div>
  8. <div class="form-wrapper">
  9. <div class="form-wrapper-main">
  10. <el-form ref="ruleFormRef" :model="activityModel" :rules="rules" class="formBox" label-width="140px">
  11. <div class="form-content">
  12. <!-- 活动类型 -->
  13. <el-form-item label="活动类型" prop="activityType">
  14. <el-select v-model="activityModel.activityType" class="form-input" clearable placeholder="请选择">
  15. <el-option label="评论有礼" :value="2" />
  16. <el-option label="营销活动" :value="1" />
  17. </el-select>
  18. </el-form-item>
  19. <!-- 活动名称 -->
  20. <el-form-item label="活动名称" prop="activityName">
  21. <el-input v-model="activityModel.activityName" class="form-input" clearable maxlength="50" placeholder="请输入" />
  22. </el-form-item>
  23. <!-- 活动时间 -->
  24. <el-form-item class="activity-time-item" label="活动时间" prop="activityTimeRange">
  25. <el-date-picker
  26. v-model="activityModel.activityTimeRange"
  27. :disabled-date="disabledDate"
  28. class="form-input"
  29. end-placeholder="结束日期"
  30. format="YYYY/MM/DD"
  31. range-separator="-"
  32. start-placeholder="开始日期"
  33. type="daterange"
  34. value-format="YYYY-MM-DD"
  35. />
  36. </el-form-item>
  37. <!-- 评论有礼相关字段 -->
  38. <template v-if="activityModel.activityType === 2">
  39. <!-- 用户可参与次数 -->
  40. <el-form-item label="用户可参与次数" prop="participationLimit">
  41. <el-input v-model="activityModel.participationLimit" placeholder="请输入" maxlength="4" />
  42. </el-form-item>
  43. <!-- 活动规则 -->
  44. <el-form-item label="活动规则" prop="activityRule">
  45. <el-cascader
  46. v-model="activityModel.activityRule"
  47. :options="ruleCascaderOptions"
  48. :props="cascaderProps"
  49. class="form-input"
  50. clearable
  51. placeholder="请选择"
  52. style="width: 100%"
  53. />
  54. </el-form-item>
  55. <!-- 优惠券 -->
  56. <el-form-item label="优惠券" prop="couponId">
  57. <el-select v-model="activityModel.couponId" class="form-input" clearable filterable placeholder="请选择">
  58. <el-option v-for="item in couponList" :key="item.id" :label="item.name" :value="item.id" />
  59. </el-select>
  60. </el-form-item>
  61. <!-- 优惠券发放数量 -->
  62. <el-form-item label="优惠券发放数量" prop="couponQuantity">
  63. <el-input v-model="activityModel.couponQuantity" placeholder="请输入" maxlength="5" />
  64. </el-form-item>
  65. </template>
  66. <!-- 营销活动相关字段 -->
  67. <template v-if="activityModel.activityType === 1">
  68. <!-- 报名时间 -->
  69. <el-form-item class="activity-time-item" label="报名时间" prop="signupTimeRange">
  70. <el-date-picker
  71. v-model="activityModel.signupTimeRange"
  72. :disabled-date="disabledDate"
  73. class="form-input"
  74. end-placeholder="结束日期"
  75. format="YYYY/MM/DD"
  76. range-separator="-"
  77. start-placeholder="开始日期"
  78. type="daterange"
  79. value-format="YYYY-MM-DD"
  80. />
  81. </el-form-item>
  82. <!-- 活动限制人数 -->
  83. <el-form-item label="活动限制人数">
  84. <el-input
  85. v-model="activityModel.activityLimitPeople"
  86. style="width: 94%"
  87. placeholder="请输入"
  88. maxlength="20"
  89. @input="handlePositiveIntegerInput('activityLimitPeople', $event)"
  90. />
  91. <div style="width: 6%; text-align: right">人</div>
  92. </el-form-item>
  93. <!-- 活动详情 -->
  94. <el-form-item label="活动详情" prop="activityDetails">
  95. <el-input
  96. v-model="activityModel.activityDetails"
  97. type="textarea"
  98. :rows="6"
  99. placeholder="请输入活动详情"
  100. maxlength="1000"
  101. show-word-limit
  102. />
  103. </el-form-item>
  104. </template>
  105. <!-- 上传图片方式 -->
  106. <el-form-item label="活动图片类型" prop="uploadImgType">
  107. <el-radio-group v-model="activityModel.uploadImgType">
  108. <el-radio :label="1"> 本地上传 </el-radio>
  109. <el-radio :label="2"> AI生成 </el-radio>
  110. </el-radio-group>
  111. </el-form-item>
  112. <!-- 图片描述(当选择使用描述时显示) -->
  113. <el-form-item v-if="activityModel.uploadImgType === 2" label="图片描述" prop="imgDescribe">
  114. <el-input
  115. v-model="activityModel.imgDescribe"
  116. type="textarea"
  117. :rows="4"
  118. placeholder="请输入图片描述"
  119. maxlength="500"
  120. show-word-limit
  121. />
  122. </el-form-item>
  123. <!-- 活动标题图 -->
  124. <el-form-item v-if="activityModel.uploadImgType === 1" label="活动标题图" prop="activityTitleImage">
  125. <div class="upload-item-wrapper">
  126. <div class="upload-area upload-area-horizontal-21-9" :class="{ 'upload-full': titleFileList.length >= 1 }">
  127. <el-upload
  128. v-model:file-list="titleFileList"
  129. :accept="'.jpg,.jpeg,.png'"
  130. :auto-upload="false"
  131. :before-remove="handleBeforeRemove"
  132. :disabled="hasUnuploadedImages"
  133. :limit="1"
  134. :on-change="handleTitleUploadChange"
  135. :on-exceed="handleUploadExceed"
  136. :on-preview="handlePictureCardPreview"
  137. :on-remove="handleTitleRemove"
  138. :show-file-list="true"
  139. list-type="picture-card"
  140. >
  141. <template #trigger>
  142. <div v-if="titleFileList.length < 1" class="upload-trigger-card el-upload--picture-card">
  143. <el-icon>
  144. <Plus />
  145. </el-icon>
  146. </div>
  147. </template>
  148. </el-upload>
  149. </div>
  150. <div class="upload-hint">请上传21:9尺寸图片效果更佳,支持jpg、jpeg、png格式,上传图片不得超过5M</div>
  151. </div>
  152. </el-form-item>
  153. <!-- 活动详情图 -->
  154. <el-form-item v-if="activityModel.uploadImgType === 1" label="活动详情图" prop="activityDetailImage">
  155. <div class="upload-item-wrapper">
  156. <div class="upload-area upload-area-vertical" :class="{ 'upload-full': detailFileList.length >= 9 }">
  157. <el-upload
  158. v-model:file-list="detailFileList"
  159. :accept="'.jpg,.jpeg,.png'"
  160. :auto-upload="false"
  161. :before-remove="handleBeforeRemove"
  162. :disabled="hasUnuploadedImages"
  163. :limit="9"
  164. :on-change="handleDetailUploadChange"
  165. :on-exceed="handleDetailUploadExceed"
  166. :on-preview="handlePictureCardPreview"
  167. :on-remove="handleDetailRemove"
  168. :show-file-list="true"
  169. list-type="picture-card"
  170. >
  171. <template #trigger>
  172. <div v-if="detailFileList.length < 9" class="upload-trigger-card el-upload--picture-card">
  173. <el-icon>
  174. <Plus />
  175. </el-icon>
  176. </div>
  177. </template>
  178. </el-upload>
  179. </div>
  180. <div class="upload-hint">请上传竖版图片,支持jpg、jpeg、png格式,最多上传9张,单张图片不得超过5M</div>
  181. </div>
  182. </el-form-item>
  183. </div>
  184. </el-form>
  185. </div>
  186. <!-- 底部按钮区域 -->
  187. <div class="button-container">
  188. <el-button @click="goBack"> 取消 </el-button>
  189. <el-button type="primary" @click="handleSubmit()"> 提交审核 </el-button>
  190. </div>
  191. </div>
  192. <!-- 图片预览 -->
  193. <el-image-viewer
  194. v-if="imageViewerVisible"
  195. :initial-index="imageViewerInitialIndex"
  196. :url-list="imageViewerUrlList"
  197. @close="imageViewerVisible = false"
  198. />
  199. </div>
  200. </template>
  201. <script lang="tsx" name="newActivity" setup>
  202. /**
  203. * 运营活动管理 - 新增/编辑页面
  204. * 功能:支持运营活动的新增和编辑操作
  205. */
  206. import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
  207. import type { FormInstance, UploadFile, UploadProps } from "element-plus";
  208. import { ElMessage, ElMessageBox } from "element-plus";
  209. import { Plus } from "@element-plus/icons-vue";
  210. import { useRoute, useRouter } from "vue-router";
  211. import {
  212. addActivity,
  213. getActivityDetail,
  214. getActivityRuleOptions,
  215. getCouponList,
  216. updateActivity
  217. } from "@/api/modules/operationManagement";
  218. import { uploadContractImage } from "@/api/modules/licenseManagement";
  219. import { localGet } from "@/utils";
  220. // ==================== 响应式数据定义 ====================
  221. // 路由相关
  222. const router = useRouter();
  223. const route = useRoute();
  224. // 页面状态
  225. const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
  226. const id = ref<string>(""); // 页面ID参数
  227. // 表单引用
  228. const ruleFormRef = ref<FormInstance>();
  229. // 优惠券列表
  230. const couponList = ref<any[]>([]);
  231. // 文件上传相关
  232. const titleFileList = ref<UploadFile[]>([]);
  233. const detailFileList = ref<UploadFile[]>([]);
  234. const titleImageUrl = ref<string>("");
  235. const detailImageUrl = ref<string>("");
  236. const pendingUploadFiles = ref<UploadFile[]>([]);
  237. const uploading = ref(false);
  238. const imageViewerVisible = ref(false);
  239. const imageViewerUrlList = ref<string[]>([]);
  240. const imageViewerInitialIndex = ref(0);
  241. // 活动规则级联选择器选项
  242. const ruleCascaderOptions = ref<any[]>([]);
  243. // 级联选择器配置
  244. const cascaderProps = {
  245. expandTrigger: "hover" as const,
  246. emitPath: true,
  247. disabled: (data: any) => {
  248. // 除了 "当用户 > 核销并评论 > 优惠券" 这个路径,其余选项不可选择
  249. if (data.disabled !== undefined) {
  250. return data.disabled;
  251. }
  252. return false;
  253. }
  254. };
  255. // 是否有未上传的图片
  256. const hasUnuploadedImages = computed(() => {
  257. return (
  258. titleFileList.value.some(file => file.status === "ready" || file.status === "uploading") ||
  259. detailFileList.value.some(file => file.status === "ready" || file.status === "uploading")
  260. );
  261. });
  262. // ==================== 表单验证规则 ====================
  263. const rules = reactive({
  264. activityType: [{ required: true, message: "请选择活动类型", trigger: "change" }],
  265. activityName: [{ required: true, message: "请输入活动名称", trigger: "blur" }],
  266. activityTimeRange: [
  267. { required: true, message: "请选择活动时间", trigger: "change" },
  268. {
  269. validator: (rule: any, value: any, callback: any) => {
  270. if (!value || !Array.isArray(value) || value.length !== 2) {
  271. callback(new Error("请选择活动时间"));
  272. return;
  273. }
  274. const [startTime, endTime] = value;
  275. if (!startTime || !endTime) {
  276. callback(new Error("请选择完整的活动时间"));
  277. return;
  278. }
  279. const start = new Date(startTime);
  280. const end = new Date(endTime);
  281. const today = new Date();
  282. today.setHours(0, 0, 0, 0);
  283. if (start < today) {
  284. callback(new Error("活动开始时间不能早于当前时间"));
  285. return;
  286. }
  287. if (start >= end) {
  288. callback(new Error("活动开始时间必须早于活动结束时间"));
  289. return;
  290. }
  291. callback();
  292. },
  293. trigger: "change"
  294. }
  295. ],
  296. participationLimit: [
  297. { required: true, message: "请输入用户可参与次数", trigger: "blur" },
  298. {
  299. validator: (rule: any, value: any, callback: any) => {
  300. if (activityModel.value.activityType === 2) {
  301. if (!value) {
  302. callback(new Error("请输入用户可参与次数"));
  303. return;
  304. }
  305. const numValue = Number(value);
  306. if (isNaN(numValue) || !Number.isInteger(numValue) || numValue <= 0) {
  307. callback(new Error("用户可参与次数必须为正整数"));
  308. return;
  309. }
  310. if (numValue > 9999) {
  311. callback(new Error("用户可参与次数必须小于9999"));
  312. return;
  313. }
  314. }
  315. callback();
  316. },
  317. trigger: "blur"
  318. }
  319. ],
  320. activityRule: [
  321. { required: true, message: "请选择活动规则", trigger: "change" },
  322. {
  323. validator: (rule: any, value: any, callback: any) => {
  324. if (activityModel.value.activityType === 2) {
  325. if (!value || !Array.isArray(value) || value.length < 2) {
  326. callback(new Error("请选择完整的活动规则(至少选择角色和行为)"));
  327. return;
  328. }
  329. }
  330. callback();
  331. },
  332. trigger: "change"
  333. }
  334. ],
  335. couponId: [
  336. { required: true, message: "请选择优惠券", trigger: "change" },
  337. {
  338. validator: (rule: any, value: any, callback: any) => {
  339. if (activityModel.value.activityType === 2) {
  340. if (!value) {
  341. callback(new Error("请选择优惠券"));
  342. return;
  343. }
  344. }
  345. callback();
  346. },
  347. trigger: "change"
  348. }
  349. ],
  350. couponQuantity: [
  351. { required: true, message: "请输入优惠券发放数量", trigger: "blur" },
  352. {
  353. validator: (rule: any, value: any, callback: any) => {
  354. if (activityModel.value.activityType === 2) {
  355. if (!value) {
  356. callback(new Error("请输入优惠券发放数量"));
  357. return;
  358. }
  359. const numValue = Number(value);
  360. if (isNaN(numValue) || !Number.isInteger(numValue) || numValue <= 0) {
  361. callback(new Error("优惠券发放数量必须为正整数"));
  362. return;
  363. }
  364. if (numValue > 99999) {
  365. callback(new Error("优惠券发放数量必须小于99999"));
  366. return;
  367. }
  368. }
  369. callback();
  370. },
  371. trigger: "blur"
  372. }
  373. ],
  374. signupTimeRange: [
  375. { required: true, message: "请选择报名时间", trigger: "change" },
  376. {
  377. validator: (rule: any, value: any, callback: any) => {
  378. if (activityModel.value.activityType === 1) {
  379. if (!value || !Array.isArray(value) || value.length !== 2) {
  380. callback(new Error("请选择报名时间"));
  381. return;
  382. }
  383. const [startTime, endTime] = value;
  384. if (!startTime || !endTime) {
  385. callback(new Error("请选择完整的报名时间"));
  386. return;
  387. }
  388. const start = new Date(startTime);
  389. const end = new Date(endTime);
  390. if (start >= end) {
  391. callback(new Error("报名开始时间必须早于报名结束时间"));
  392. return;
  393. }
  394. }
  395. callback();
  396. },
  397. trigger: "change"
  398. }
  399. ],
  400. activityDetails: [
  401. { required: true, message: "请输入活动详情", trigger: ["blur", "change"] },
  402. {
  403. validator: (rule: any, value: any, callback: any) => {
  404. if (activityModel.value.activityType === 1) {
  405. if (!value || value.trim() === "") {
  406. callback(new Error("请输入活动详情"));
  407. return;
  408. }
  409. }
  410. callback();
  411. },
  412. trigger: ["blur", "change"]
  413. }
  414. ],
  415. uploadImgType: [{ required: true, message: "请选择上传图片方式", trigger: "change" }],
  416. imgDescribe: [
  417. {
  418. required: true,
  419. validator: (rule: any, value: any, callback: any) => {
  420. if (activityModel.value.uploadImgType === 2) {
  421. if (!value || value.trim() === "") {
  422. callback(new Error("请输入图片描述"));
  423. return;
  424. }
  425. }
  426. callback();
  427. },
  428. trigger: ["blur", "change"]
  429. }
  430. ],
  431. activityTitleImage: [
  432. {
  433. required: true,
  434. validator: (rule: any, value: any, callback: any) => {
  435. if (activityModel.value.uploadImgType === 1) {
  436. if (!titleImageUrl.value) {
  437. callback(new Error("请上传活动标题图"));
  438. return;
  439. }
  440. }
  441. callback();
  442. },
  443. trigger: ["change", "blur"]
  444. }
  445. ],
  446. activityDetailImage: [
  447. {
  448. required: true,
  449. validator: (rule: any, value: any, callback: any) => {
  450. if (activityModel.value.uploadImgType === 1) {
  451. // 检查是否有成功上传的图片
  452. const successFiles = detailFileList.value.filter((f: any) => f.status === "success" && f.url);
  453. if (successFiles.length === 0) {
  454. callback(new Error("请上传活动详情图"));
  455. return;
  456. }
  457. }
  458. callback();
  459. },
  460. trigger: ["change", "blur"]
  461. }
  462. ]
  463. });
  464. // ==================== 活动信息数据模型 ====================
  465. const activityModel = ref<any>({
  466. // 活动类型:1-营销活动,2-评论有礼
  467. activityType: 1,
  468. // 活动宣传图(包含标题和详情)
  469. promotionImages: null,
  470. // 活动标题图片
  471. activityTitleImg: null,
  472. // 活动详情图片
  473. activityDetailImg: null,
  474. // 活动标题图(用于表单验证)
  475. activityTitleImage: null,
  476. // 活动详情图(用于表单验证)
  477. activityDetailImage: null,
  478. // 活动名称
  479. activityName: "",
  480. // 活动时间范围
  481. activityTimeRange: [],
  482. // 用户可参与次数(评论有礼)
  483. participationLimit: "",
  484. // 活动规则(级联选择器的值数组)(评论有礼)
  485. activityRule: [],
  486. // 优惠券ID(评论有礼)
  487. couponId: "",
  488. // 优惠券发放数量(评论有礼)
  489. couponQuantity: "",
  490. // 报名时间范围(营销活动)
  491. signupTimeRange: [],
  492. // 活动限制人数(营销活动)
  493. activityLimitPeople: "",
  494. // 活动详情(营销活动)
  495. activityDetails: "",
  496. // 上传图片方式:1-正常用户,2-使用描述
  497. uploadImgType: 1,
  498. // 图片描述(当uploadImgType为2时使用)
  499. imgDescribe: ""
  500. });
  501. // ==================== 日期选择器禁用规则 ====================
  502. // 禁用日期(不能早于今天)
  503. const disabledDate = (time: Date) => {
  504. const today = new Date();
  505. today.setHours(0, 0, 0, 0);
  506. return time.getTime() < today.getTime();
  507. };
  508. // ==================== 图片参数转换函数 ====================
  509. /**
  510. * 图片URL转换为upload组件的参数
  511. * @param imageUrl 图片URL
  512. * @returns UploadFile对象
  513. */
  514. const handleImageParam = (imageUrl: string): UploadFile => {
  515. // 使用split方法以'/'为分隔符将URL拆分成数组
  516. const parts = imageUrl.split("/");
  517. // 取数组的最后一项,即图片名称
  518. const imageName = parts[parts.length - 1];
  519. return {
  520. uid: Date.now() + Math.random(),
  521. name: imageName,
  522. status: "success",
  523. percentage: 100,
  524. url: imageUrl
  525. } as unknown as UploadFile;
  526. };
  527. // ==================== 文件上传相关函数 ====================
  528. /**
  529. * 检查文件是否在排队中(未上传)
  530. */
  531. const isFilePending = (file: any): boolean => {
  532. if (file.status === "ready") {
  533. return true;
  534. }
  535. if (pendingUploadFiles.value.some(item => item.uid === file.uid)) {
  536. return true;
  537. }
  538. return false;
  539. };
  540. /**
  541. * 图片上传 - 删除前确认
  542. */
  543. const handleBeforeRemove = async (uploadFile: any, uploadFiles: any[]): Promise<boolean> => {
  544. if (isFilePending(uploadFile)) {
  545. ElMessage.warning("图片尚未上传,请等待上传完成后再删除");
  546. return false;
  547. }
  548. try {
  549. await ElMessageBox.confirm("确定要删除这张图片吗?", "提示", {
  550. confirmButtonText: "确定",
  551. cancelButtonText: "取消",
  552. type: "warning"
  553. });
  554. return true;
  555. } catch {
  556. return false;
  557. }
  558. };
  559. /**
  560. * 活动标题图片上传 - 移除图片回调
  561. */
  562. const handleTitleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {
  563. const file = uploadFile as any;
  564. const imageUrl = file.url;
  565. if (imageUrl) {
  566. titleImageUrl.value = "";
  567. activityModel.value.activityTitleImg = null;
  568. activityModel.value.activityTitleImage = null;
  569. // 触发表单验证
  570. nextTick(() => {
  571. ruleFormRef.value?.validateField("activityTitleImage");
  572. });
  573. }
  574. if (file.url && file.url.startsWith("blob:")) {
  575. URL.revokeObjectURL(file.url);
  576. }
  577. titleFileList.value = [...uploadFiles];
  578. ElMessage.success("图片已删除");
  579. };
  580. /**
  581. * 活动详情图片上传 - 移除图片回调
  582. */
  583. const handleDetailRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {
  584. const file = uploadFile as any;
  585. const imageUrl = file.url;
  586. // 更新图片URL列表(移除已删除的图片)
  587. const successFiles = uploadFiles.filter((f: any) => f.status === "success" && f.url);
  588. const imageUrls = successFiles.map((f: any) => f.url);
  589. detailImageUrl.value = imageUrls.length > 0 ? imageUrls.join(",") : "";
  590. activityModel.value.activityDetailImg = imageUrls.length > 0 ? { url: imageUrls.join(",") } : null;
  591. activityModel.value.activityDetailImage = imageUrls.length > 0 ? imageUrls.join(",") : "";
  592. // 触发表单验证
  593. nextTick(() => {
  594. ruleFormRef.value?.validateField("activityDetailImage");
  595. });
  596. if (file.url && file.url.startsWith("blob:")) {
  597. URL.revokeObjectURL(file.url);
  598. }
  599. detailFileList.value = [...uploadFiles];
  600. ElMessage.success("图片已删除");
  601. };
  602. /**
  603. * 上传文件超出限制提示(标题图)
  604. */
  605. const handleUploadExceed: UploadProps["onExceed"] = () => {
  606. ElMessage.warning("最多只能上传1张图片");
  607. };
  608. /**
  609. * 活动详情图上传超出限制提示
  610. */
  611. const handleDetailUploadExceed: UploadProps["onExceed"] = () => {
  612. ElMessage.warning("最多只能上传9张图片");
  613. };
  614. /**
  615. * 活动标题图片上传 - 文件变更
  616. */
  617. const handleTitleUploadChange: UploadProps["onChange"] = async (uploadFile, uploadFiles) => {
  618. if (uploadFile.raw) {
  619. const fileType = uploadFile.raw.type.toLowerCase();
  620. const fileName = uploadFile.name.toLowerCase();
  621. const validTypes = ["image/jpeg", "image/jpg", "image/png"];
  622. const validExtensions = [".jpg", ".jpeg", ".png"];
  623. const isValidType = validTypes.includes(fileType) || validExtensions.some(ext => fileName.endsWith(ext));
  624. if (!isValidType) {
  625. // 从文件列表中移除不符合类型的文件
  626. const index = titleFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
  627. if (index > -1) {
  628. titleFileList.value.splice(index, 1);
  629. }
  630. // 从 uploadFiles 中移除
  631. const uploadIndex = uploadFiles.findIndex((f: any) => f.uid === uploadFile.uid);
  632. if (uploadIndex > -1) {
  633. uploadFiles.splice(uploadIndex, 1);
  634. }
  635. // 如果文件有 blob URL,释放它
  636. if (uploadFile.url && uploadFile.url.startsWith("blob:")) {
  637. URL.revokeObjectURL(uploadFile.url);
  638. }
  639. ElMessage.warning("只支持上传 JPG、JPEG 和 PNG 格式的图片");
  640. return;
  641. }
  642. // 检查文件大小,不得超过5M
  643. const maxSize = 5 * 1024 * 1024; // 5MB
  644. if (uploadFile.raw.size > maxSize) {
  645. // 从文件列表中移除超过大小的文件
  646. const index = titleFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
  647. if (index > -1) {
  648. titleFileList.value.splice(index, 1);
  649. }
  650. // 从 uploadFiles 中移除
  651. const uploadIndex = uploadFiles.findIndex((f: any) => f.uid === uploadFile.uid);
  652. if (uploadIndex > -1) {
  653. uploadFiles.splice(uploadIndex, 1);
  654. }
  655. // 如果文件有 blob URL,释放它
  656. if (uploadFile.url && uploadFile.url.startsWith("blob:")) {
  657. URL.revokeObjectURL(uploadFile.url);
  658. }
  659. ElMessage.warning("上传图片不得超过5M");
  660. return;
  661. }
  662. }
  663. const existingIndex = titleFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
  664. if (existingIndex === -1) {
  665. titleFileList.value.push(uploadFile);
  666. }
  667. const readyFiles = titleFileList.value.filter(file => file.status === "ready");
  668. if (readyFiles.length) {
  669. readyFiles.forEach(file => {
  670. if (!pendingUploadFiles.value.some(item => item.uid === file.uid)) {
  671. pendingUploadFiles.value.push(file);
  672. }
  673. });
  674. }
  675. processUploadQueue("title");
  676. };
  677. /**
  678. * 活动详情图片上传 - 文件变更
  679. */
  680. const handleDetailUploadChange: UploadProps["onChange"] = async (uploadFile, uploadFiles) => {
  681. if (uploadFile.raw) {
  682. const fileType = uploadFile.raw.type.toLowerCase();
  683. const fileName = uploadFile.name.toLowerCase();
  684. const validTypes = ["image/jpeg", "image/jpg", "image/png"];
  685. const validExtensions = [".jpg", ".jpeg", ".png"];
  686. const isValidType = validTypes.includes(fileType) || validExtensions.some(ext => fileName.endsWith(ext));
  687. if (!isValidType) {
  688. // 从文件列表中移除不符合类型的文件
  689. const index = detailFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
  690. if (index > -1) {
  691. detailFileList.value.splice(index, 1);
  692. }
  693. // 从 uploadFiles 中移除
  694. const uploadIndex = uploadFiles.findIndex((f: any) => f.uid === uploadFile.uid);
  695. if (uploadIndex > -1) {
  696. uploadFiles.splice(uploadIndex, 1);
  697. }
  698. // 如果文件有 blob URL,释放它
  699. if (uploadFile.url && uploadFile.url.startsWith("blob:")) {
  700. URL.revokeObjectURL(uploadFile.url);
  701. }
  702. ElMessage.warning("只支持上传 JPG、JPEG 和 PNG 格式的图片");
  703. return;
  704. }
  705. // 检查文件大小,不得超过5M
  706. const maxSize = 5 * 1024 * 1024; // 5MB
  707. if (uploadFile.raw.size > maxSize) {
  708. // 从文件列表中移除超过大小的文件
  709. const index = detailFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
  710. if (index > -1) {
  711. detailFileList.value.splice(index, 1);
  712. }
  713. // 从 uploadFiles 中移除
  714. const uploadIndex = uploadFiles.findIndex((f: any) => f.uid === uploadFile.uid);
  715. if (uploadIndex > -1) {
  716. uploadFiles.splice(uploadIndex, 1);
  717. }
  718. // 如果文件有 blob URL,释放它
  719. if (uploadFile.url && uploadFile.url.startsWith("blob:")) {
  720. URL.revokeObjectURL(uploadFile.url);
  721. }
  722. ElMessage.warning("上传图片不得超过5M");
  723. return;
  724. }
  725. }
  726. const existingIndex = detailFileList.value.findIndex((f: any) => f.uid === uploadFile.uid);
  727. if (existingIndex === -1) {
  728. detailFileList.value.push(uploadFile);
  729. }
  730. const readyFiles = detailFileList.value.filter(file => file.status === "ready");
  731. if (readyFiles.length) {
  732. readyFiles.forEach(file => {
  733. if (!pendingUploadFiles.value.some(item => item.uid === file.uid)) {
  734. pendingUploadFiles.value.push(file);
  735. }
  736. });
  737. }
  738. processUploadQueue("detail");
  739. };
  740. /**
  741. * 处理上传队列 - 逐个上传文件
  742. */
  743. const processUploadQueue = async (type: string) => {
  744. if (uploading.value || pendingUploadFiles.value.length === 0) {
  745. return;
  746. }
  747. const file = pendingUploadFiles.value.shift();
  748. if (file) {
  749. await uploadSingleFile(file, type);
  750. processUploadQueue(type);
  751. }
  752. };
  753. /**
  754. * 单文件上传图片
  755. */
  756. const uploadSingleFile = async (file: UploadFile, uploadType: string) => {
  757. if (!file.raw) {
  758. return;
  759. }
  760. const rawFile = file.raw as File;
  761. const formData = new FormData();
  762. formData.append("file", rawFile);
  763. formData.append("user", "text");
  764. file.status = "uploading";
  765. file.percentage = 0;
  766. uploading.value = true;
  767. try {
  768. const result: any = await uploadContractImage(formData);
  769. if (result?.code === 200 && result.data) {
  770. let imageUrl = result.data[0];
  771. if (!imageUrl) {
  772. throw new Error("上传成功但未获取到图片URL");
  773. }
  774. file.status = "success";
  775. file.percentage = 100;
  776. file.url = imageUrl;
  777. file.response = { url: imageUrl };
  778. if (uploadType === "title") {
  779. titleImageUrl.value = imageUrl;
  780. activityModel.value.activityTitleImg = { url: imageUrl };
  781. activityModel.value.activityTitleImage = imageUrl;
  782. // 触发表单验证
  783. nextTick(() => {
  784. ruleFormRef.value?.validateField("activityTitleImage");
  785. });
  786. } else if (uploadType === "detail") {
  787. // 支持多张图片,将所有成功上传的图片URL组合
  788. const successFiles = detailFileList.value.filter((f: any) => f.status === "success" && f.url);
  789. const imageUrls = successFiles.map((f: any) => f.url);
  790. // 如果有多张图片,用逗号连接;如果只有一张,保持字符串格式
  791. detailImageUrl.value = imageUrls.length > 0 ? imageUrls.join(",") : "";
  792. activityModel.value.activityDetailImg = imageUrls.length > 0 ? { url: imageUrls.join(",") } : null;
  793. activityModel.value.activityDetailImage = imageUrls.length > 0 ? imageUrls.join(",") : "";
  794. // 触发表单验证
  795. nextTick(() => {
  796. ruleFormRef.value?.validateField("activityDetailImage");
  797. });
  798. }
  799. } else {
  800. throw new Error(result?.msg || "图片上传失败");
  801. }
  802. } catch (error: any) {
  803. // 上传失败时保持进度条为 0
  804. file.percentage = 0;
  805. // 不要设置 file.status = "fail",直接移除文件,避免显示错误占位图
  806. if (file.url && file.url.startsWith("blob:")) {
  807. URL.revokeObjectURL(file.url);
  808. }
  809. // 从文件列表中移除失败的文件
  810. const index =
  811. uploadType === "title"
  812. ? titleFileList.value.findIndex((f: any) => f.uid === file.uid)
  813. : detailFileList.value.findIndex((f: any) => f.uid === file.uid);
  814. if (index > -1) {
  815. if (uploadType === "title") {
  816. titleFileList.value.splice(index, 1);
  817. } else {
  818. detailFileList.value.splice(index, 1);
  819. }
  820. }
  821. // Error message handled by global upload method, except for specific business logic errors
  822. if (error?.message && error.message.includes("未获取到图片URL")) {
  823. ElMessage.error(error.message);
  824. }
  825. } finally {
  826. uploading.value = false;
  827. // 触发视图更新
  828. if (uploadType === "title") {
  829. titleFileList.value = [...titleFileList.value];
  830. } else {
  831. detailFileList.value = [...detailFileList.value];
  832. }
  833. }
  834. };
  835. /**
  836. * 图片预览
  837. */
  838. const handlePictureCardPreview = (file: any) => {
  839. if (isFilePending(file)) {
  840. ElMessage.warning("图片尚未上传,请等待上传完成后再预览");
  841. return;
  842. }
  843. if (file.status === "uploading" && file.url) {
  844. imageViewerUrlList.value = [file.url];
  845. imageViewerInitialIndex.value = 0;
  846. imageViewerVisible.value = true;
  847. return;
  848. }
  849. const allFiles = [...titleFileList.value, ...detailFileList.value];
  850. const urlList = allFiles
  851. .filter((item: any) => item.status === "success" && (item.url || item.response?.data))
  852. .map((item: any) => item.url || item.response?.data);
  853. const currentIndex = urlList.findIndex((url: string) => url === (file.url || file.response?.data));
  854. if (currentIndex < 0) {
  855. ElMessage.warning("图片尚未上传完成,无法预览");
  856. return;
  857. }
  858. imageViewerUrlList.value = urlList;
  859. imageViewerInitialIndex.value = currentIndex;
  860. imageViewerVisible.value = true;
  861. };
  862. // ==================== 监听器 ====================
  863. /**
  864. * 监听活动类型变化,切换时清除相关数据并重新验证
  865. */
  866. watch(
  867. () => activityModel.value.activityType,
  868. (newVal, oldVal) => {
  869. // 如果 oldVal 为空或 undefined,说明是初始化,不处理
  870. if (oldVal === undefined || oldVal === null || oldVal === "") return;
  871. // 如果新旧值相同,不处理
  872. if (newVal === oldVal) return;
  873. nextTick(() => {
  874. if (newVal === 1) {
  875. // 切换到营销活动:清除评论有礼相关字段
  876. activityModel.value.participationLimit = "";
  877. activityModel.value.activityRule = [];
  878. activityModel.value.couponId = "";
  879. activityModel.value.couponQuantity = "";
  880. // 清除评论有礼字段的验证状态
  881. ruleFormRef.value?.clearValidate(["participationLimit", "activityRule", "couponId", "couponQuantity"]);
  882. } else if (newVal === 2) {
  883. // 切换到评论有礼:清除营销活动相关字段
  884. activityModel.value.signupTimeRange = [];
  885. activityModel.value.activityLimitPeople = "";
  886. activityModel.value.activityDetails = "";
  887. // 清除营销活动字段的验证状态
  888. ruleFormRef.value?.clearValidate(["signupTimeRange", "activityLimitPeople", "activityDetails"]);
  889. }
  890. });
  891. },
  892. { immediate: false }
  893. );
  894. /**
  895. * 监听上传图片方式变化,切换时清除相关数据并重新验证
  896. */
  897. watch(
  898. () => activityModel.value.uploadImgType,
  899. (newVal, oldVal) => {
  900. if (oldVal === undefined) return; // 初始化时不处理
  901. nextTick(() => {
  902. if (newVal === 2) {
  903. // 切换到使用描述:清除图片数据
  904. titleFileList.value = [];
  905. detailFileList.value = [];
  906. titleImageUrl.value = "";
  907. detailImageUrl.value = "";
  908. activityModel.value.activityTitleImg = null;
  909. activityModel.value.activityDetailImg = null;
  910. activityModel.value.activityTitleImage = null;
  911. activityModel.value.activityDetailImage = null;
  912. // 清除图片字段的验证
  913. ruleFormRef.value?.clearValidate(["activityTitleImage", "activityDetailImage"]);
  914. // 验证描述字段
  915. ruleFormRef.value?.validateField("imgDescribe");
  916. } else if (newVal === 1) {
  917. // 切换到正常用户:清除描述数据
  918. activityModel.value.imgDescribe = "";
  919. // 清除描述字段的验证
  920. ruleFormRef.value?.clearValidate("imgDescribe");
  921. // 验证图片字段
  922. ruleFormRef.value?.validateField(["activityTitleImage", "activityDetailImage"]);
  923. }
  924. });
  925. }
  926. );
  927. // ==================== 生命周期钩子 ====================
  928. /**
  929. * 组件挂载时初始化
  930. */
  931. onMounted(async () => {
  932. id.value = (route.query.id as string) || "";
  933. type.value = (route.query.type as string) || "";
  934. // 加载优惠券列表
  935. try {
  936. const params = {
  937. storeId: localGet("createdId"),
  938. groupType: localGet("businessSection"),
  939. couponType: "2",
  940. couponStatus: "1",
  941. couponsFromType: 1,
  942. pageNum: 1,
  943. pageSize: 99999
  944. };
  945. const res: any = await getCouponList(params);
  946. if (res && res.code == 200) {
  947. couponList.value = res.data?.discountList?.records || [];
  948. }
  949. } catch (error) {
  950. console.error("加载优惠券列表失败:", error);
  951. }
  952. // 加载活动规则级联选择器选项
  953. try {
  954. const res: any = await getActivityRuleOptions({ page: 1, size: 99999 });
  955. console.log("ruleCascaderOptions:", res.data);
  956. if (res && res.code == 200) {
  957. ruleCascaderOptions.value = res.data || [];
  958. }
  959. } catch (error) {
  960. console.error("加载活动规则选项失败:", error);
  961. }
  962. // 编辑模式下加载数据
  963. if (type.value != "add" && id.value) {
  964. try {
  965. const res: any = await getActivityDetail({ id: id.value });
  966. if (res && res.code == 200) {
  967. // 先设置 activityType 为数字类型,确保下拉框正确显示
  968. if (res.data.activityType !== undefined) {
  969. activityModel.value.activityType = Number(res.data.activityType);
  970. }
  971. // 合并其他数据
  972. activityModel.value.activityName = res.data.activityName || "";
  973. // 处理活动时间范围
  974. if (res.data.startTime && res.data.endTime) {
  975. // 只取日期部分(去掉时分秒)
  976. const startDate = res.data.startTime.split(" ")[0];
  977. const endDate = res.data.endTime.split(" ")[0];
  978. activityModel.value.activityTimeRange = [startDate, endDate];
  979. }
  980. // 根据活动类型加载对应字段
  981. if (activityModel.value.activityType === 1) {
  982. // 营销活动:加载报名时间、活动限制人数、活动详情
  983. if (res.data.signupStartTime && res.data.signupEndTime) {
  984. // 只取日期部分(去掉时分秒)
  985. const signupStartDate = res.data.signupStartTime.split(" ")[0];
  986. const signupEndDate = res.data.signupEndTime.split(" ")[0];
  987. activityModel.value.signupTimeRange = [signupStartDate, signupEndDate];
  988. } else {
  989. activityModel.value.signupTimeRange = [];
  990. }
  991. // 加载活动限制人数
  992. if (res.data.activityLimitPeople !== undefined && res.data.activityLimitPeople !== null) {
  993. activityModel.value.activityLimitPeople = String(res.data.activityLimitPeople);
  994. } else {
  995. activityModel.value.activityLimitPeople = "";
  996. }
  997. // 加载活动详情
  998. activityModel.value.activityDetails = res.data.activityDetails || "";
  999. } else if (activityModel.value.activityType === 2) {
  1000. // 评论有礼:加载活动规则、优惠券、优惠券发放数量、用户可参与次数
  1001. // 加载活动规则
  1002. if (res.data.activityRule) {
  1003. activityModel.value.activityRule = res.data.activityRule.split(",");
  1004. } else {
  1005. activityModel.value.activityRule = [];
  1006. }
  1007. // 加载优惠券ID
  1008. activityModel.value.couponId = res.data.couponId || "";
  1009. // 加载优惠券发放数量
  1010. if (res.data.couponQuantity !== undefined && res.data.couponQuantity !== null) {
  1011. activityModel.value.couponQuantity = String(res.data.couponQuantity);
  1012. } else {
  1013. activityModel.value.couponQuantity = "";
  1014. }
  1015. // 加载用户可参与次数
  1016. if (res.data.participationLimit !== undefined && res.data.participationLimit !== null) {
  1017. activityModel.value.participationLimit = String(res.data.participationLimit);
  1018. } else {
  1019. activityModel.value.participationLimit = "";
  1020. }
  1021. }
  1022. // 加载上传图片方式
  1023. if (res.data.uploadImgType !== undefined) {
  1024. activityModel.value.uploadImgType = res.data.uploadImgType;
  1025. } else {
  1026. activityModel.value.uploadImgType = 1; // 默认为正常用户
  1027. }
  1028. // 加载图片描述
  1029. if (res.data.imgDescribe) {
  1030. activityModel.value.imgDescribe = res.data.imgDescribe;
  1031. } else {
  1032. activityModel.value.imgDescribe = "";
  1033. }
  1034. // 根据上传图片方式决定是否加载图片数据
  1035. if (activityModel.value.uploadImgType === 1) {
  1036. // 如果有标题图片,添加到文件列表
  1037. if (res.data.activityTitleImgUrl) {
  1038. const titleImgUrl = res.data.activityTitleImgUrl;
  1039. if (titleImgUrl) {
  1040. titleImageUrl.value = titleImgUrl;
  1041. activityModel.value.activityTitleImg = res.data.activityTitleImgUrl;
  1042. activityModel.value.activityTitleImage = titleImgUrl;
  1043. const titleFile = handleImageParam(titleImgUrl);
  1044. titleFileList.value = [titleFile];
  1045. }
  1046. }
  1047. // 如果有详情图片,添加到文件列表(支持多张图片,可能是字符串或数组)
  1048. if (res.data.activityDetailImgUrl) {
  1049. let detailImgUrls: string[] = [];
  1050. // 如果是字符串,可能是逗号分隔的多张图片
  1051. if (typeof res.data.activityDetailImgUrl === "string") {
  1052. detailImgUrls = res.data.activityDetailImgUrl.split(",").filter((url: string) => url.trim());
  1053. } else if (Array.isArray(res.data.activityDetailImgUrl)) {
  1054. detailImgUrls = res.data.activityDetailImgUrl.filter((url: any) => url);
  1055. }
  1056. if (detailImgUrls.length > 0) {
  1057. detailImageUrl.value = detailImgUrls.join(",");
  1058. activityModel.value.activityDetailImg = res.data.activityDetailImgUrl;
  1059. activityModel.value.activityDetailImage = detailImgUrls.join(",");
  1060. // 将多张图片添加到文件列表
  1061. detailFileList.value = detailImgUrls.map((url: string) => handleImageParam(url));
  1062. }
  1063. }
  1064. }
  1065. }
  1066. } catch (error) {
  1067. console.error("加载活动详情失败:", error);
  1068. ElMessage.error("加载活动详情失败");
  1069. }
  1070. }
  1071. await nextTick();
  1072. ruleFormRef.value?.clearValidate();
  1073. });
  1074. // ==================== 事件处理函数 ====================
  1075. /**
  1076. * 处理正整数输入(只允许输入正整数)
  1077. */
  1078. const handlePositiveIntegerInput = (field: string, value: string) => {
  1079. // 移除所有非数字字符
  1080. let filteredValue = value.replace(/[^\d]/g, "");
  1081. // 限制最大长度为20
  1082. if (filteredValue.length > 20) {
  1083. filteredValue = filteredValue.substring(0, 20);
  1084. }
  1085. // 更新对应字段的值
  1086. if (field === "activityLimitPeople") {
  1087. activityModel.value.activityLimitPeople = filteredValue;
  1088. }
  1089. };
  1090. /**
  1091. * 返回上一页
  1092. */
  1093. const goBack = () => {
  1094. router.go(-1);
  1095. };
  1096. /**
  1097. * 提交表单
  1098. */
  1099. const handleSubmit = async () => {
  1100. if (!ruleFormRef.value) return;
  1101. // 如果选择正常用户方式且有未上传的图片,阻止提交
  1102. if (activityModel.value.uploadImgType === 1 && hasUnuploadedImages.value) {
  1103. ElMessage.warning("请等待图片上传完成后再提交");
  1104. return;
  1105. }
  1106. await ruleFormRef.value.validate(async valid => {
  1107. if (valid) {
  1108. const [startTime, endTime] = activityModel.value.activityTimeRange || [];
  1109. const auditParam = {
  1110. text: `${activityModel.value.activityName}, ${activityModel.value.imgDescribe || ""}`,
  1111. image_urls: [titleImageUrl.value, detailImageUrl.value]
  1112. };
  1113. const params: any = {
  1114. activityType: activityModel.value.activityType,
  1115. activityName: activityModel.value.activityName,
  1116. startTime: startTime,
  1117. endTime: endTime,
  1118. uploadImgType: activityModel.value.uploadImgType,
  1119. storeId: localGet("createdId"),
  1120. groupType: localGet("businessSection"),
  1121. status: 1, // 1-待审核
  1122. auditParam: JSON.stringify(auditParam)
  1123. };
  1124. // 根据活动类型添加不同的字段,确保只提交对应类型的字段
  1125. if (activityModel.value.activityType === 1) {
  1126. // 营销活动:只添加营销活动相关字段
  1127. const [signupStartTime, signupEndTime] = activityModel.value.signupTimeRange || [];
  1128. params.signupStartTime = signupStartTime;
  1129. params.signupEndTime = signupEndTime;
  1130. params.activityLimitPeople = activityModel.value.activityLimitPeople;
  1131. params.activityDetails = activityModel.value.activityDetails;
  1132. // 确保不包含评论有礼的字段
  1133. delete params.participationLimit;
  1134. delete params.activityRule;
  1135. delete params.couponId;
  1136. delete params.couponQuantity;
  1137. } else if (activityModel.value.activityType === 2) {
  1138. // 评论有礼:只添加评论有礼相关字段
  1139. params.participationLimit = activityModel.value.participationLimit;
  1140. params.activityRule = activityModel.value.activityRule.join(",");
  1141. params.couponId = activityModel.value.couponId;
  1142. params.couponQuantity = activityModel.value.couponQuantity;
  1143. // 确保不包含营销活动的字段
  1144. delete params.signupStartTime;
  1145. delete params.signupEndTime;
  1146. delete params.activityLimitPeople;
  1147. delete params.activityDetails;
  1148. }
  1149. // 根据上传图片方式设置不同的参数
  1150. if (activityModel.value.uploadImgType === 1) {
  1151. // 正常用户:上传图片
  1152. params.activityTitleImg = {
  1153. imgUrl: titleImageUrl.value,
  1154. imgSort: 0,
  1155. storeId: localGet("createdId")
  1156. };
  1157. params.activityDetailImg = {
  1158. imgUrl: detailImageUrl.value,
  1159. imgSort: 0,
  1160. storeId: localGet("createdId")
  1161. };
  1162. } else if (activityModel.value.uploadImgType === 2) {
  1163. // 使用描述:提交描述文本,但依然保留图片字段,imgUrl为空
  1164. params.imgDescribe = activityModel.value.imgDescribe;
  1165. params.activityTitleImg = {
  1166. imgUrl: "",
  1167. imgSort: 0,
  1168. storeId: localGet("createdId")
  1169. };
  1170. params.activityDetailImg = {
  1171. imgUrl: "",
  1172. imgSort: 0,
  1173. storeId: localGet("createdId")
  1174. };
  1175. }
  1176. try {
  1177. let res: any;
  1178. if (type.value == "add") {
  1179. res = await addActivity(params);
  1180. } else {
  1181. params.id = id.value;
  1182. res = await updateActivity(params);
  1183. }
  1184. if (res && res.code == 200) {
  1185. ElMessage.success(type.value == "add" ? "新增成功" : "编辑成功");
  1186. goBack();
  1187. } else {
  1188. ElMessage.error(res?.msg || "操作失败");
  1189. }
  1190. } catch (error) {
  1191. console.error("提交失败:", error);
  1192. ElMessage.error("操作失败");
  1193. }
  1194. }
  1195. });
  1196. };
  1197. </script>
  1198. <style lang="scss" scoped>
  1199. /* 页面容器 */
  1200. .table-box {
  1201. display: flex;
  1202. flex-direction: column;
  1203. height: auto !important;
  1204. min-height: 100%;
  1205. }
  1206. /* 头部区域 */
  1207. .header {
  1208. display: flex;
  1209. align-items: center;
  1210. justify-content: center;
  1211. padding: 20px 24px;
  1212. background-color: #ffffff;
  1213. border-bottom: 1px solid #e4e7ed;
  1214. box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
  1215. }
  1216. .title {
  1217. flex: 1;
  1218. margin: 0;
  1219. font-size: 18px;
  1220. font-weight: 600;
  1221. color: #303133;
  1222. text-align: center;
  1223. }
  1224. /* 表单内容区域 */
  1225. .form-content {
  1226. padding: 24px;
  1227. background-color: #ffffff;
  1228. }
  1229. /* 表单包装器 */
  1230. .form-wrapper {
  1231. display: flex;
  1232. flex-direction: column;
  1233. align-items: center;
  1234. background-color: #ffffff;
  1235. .form-wrapper-main {
  1236. width: 100%;
  1237. max-width: 800px;
  1238. }
  1239. }
  1240. /* 上传项容器 */
  1241. .upload-item-wrapper {
  1242. display: flex;
  1243. flex-direction: column;
  1244. gap: 12px;
  1245. align-items: flex-start;
  1246. width: 100%;
  1247. }
  1248. .upload-hint {
  1249. margin-top: 4px;
  1250. font-size: 12px;
  1251. line-height: 1.5;
  1252. color: #909399;
  1253. }
  1254. /* 规则表格容器 */
  1255. .rule-table-container {
  1256. margin-top: 20px;
  1257. }
  1258. /* 底部按钮区域 */
  1259. .button-container {
  1260. display: flex;
  1261. gap: 20px;
  1262. justify-content: center;
  1263. padding-bottom: 15px;
  1264. background-color: #ffffff;
  1265. }
  1266. .upload-area {
  1267. width: 100%;
  1268. :deep(.el-upload--picture-card) {
  1269. width: 100%;
  1270. height: 180px;
  1271. }
  1272. :deep(.el-upload-list--picture-card) {
  1273. width: 100%;
  1274. .el-upload-list__item {
  1275. width: 100%;
  1276. height: 180px;
  1277. margin: 0;
  1278. }
  1279. }
  1280. :deep(.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label) {
  1281. display: inline-flex !important;
  1282. opacity: 1 !important;
  1283. }
  1284. :deep(.el-upload-list__item.is-success:focus .el-upload-list__item-status-label) {
  1285. display: inline-flex !important;
  1286. opacity: 1 !important;
  1287. }
  1288. :deep(.el-upload-list--picture-card .el-icon--close-tip) {
  1289. display: none !important;
  1290. }
  1291. &.upload-full {
  1292. :deep(.el-upload--picture-card) {
  1293. display: none !important;
  1294. }
  1295. }
  1296. // 21:9横向图片样式
  1297. &.upload-area-horizontal-21-9 {
  1298. :deep(.el-upload--picture-card) {
  1299. width: 100%;
  1300. height: auto;
  1301. aspect-ratio: 21 / 9;
  1302. }
  1303. :deep(.el-upload-list--picture-card) {
  1304. width: 100%;
  1305. .el-upload-list__item {
  1306. width: 100%;
  1307. height: auto;
  1308. aspect-ratio: 21 / 9;
  1309. margin: 0;
  1310. }
  1311. }
  1312. }
  1313. // 竖版图片样式
  1314. &.upload-area-vertical {
  1315. :deep(.el-upload--picture-card) {
  1316. width: 150px;
  1317. height: 150px;
  1318. }
  1319. :deep(.el-upload-list--picture-card) {
  1320. .el-upload-list__item {
  1321. width: 150px;
  1322. height: 150px;
  1323. margin: 0;
  1324. }
  1325. }
  1326. }
  1327. }
  1328. .upload-trigger-card {
  1329. display: flex;
  1330. flex-direction: column;
  1331. align-items: center;
  1332. justify-content: center;
  1333. width: 100%;
  1334. height: 100%;
  1335. font-size: 28px;
  1336. color: #8c939d;
  1337. }
  1338. /* el-upload 图片预览铺满容器 */
  1339. :deep(.el-upload-list--picture-card) {
  1340. .el-upload-list__item {
  1341. margin: 0;
  1342. overflow: hidden;
  1343. .el-upload-list__item-thumbnail {
  1344. width: 100%;
  1345. height: 100%;
  1346. //object-fit: fill;
  1347. }
  1348. }
  1349. .el-upload-list__item[data-status="ready"],
  1350. .el-upload-list__item.is-ready {
  1351. position: relative;
  1352. pointer-events: none;
  1353. cursor: not-allowed;
  1354. opacity: 0.6;
  1355. &::after {
  1356. position: absolute;
  1357. inset: 0;
  1358. z-index: 1;
  1359. content: "";
  1360. background-color: rgb(0 0 0 / 30%);
  1361. }
  1362. .el-upload-list__item-actions {
  1363. pointer-events: none;
  1364. opacity: 0.5;
  1365. }
  1366. }
  1367. .el-upload-list__item:hover .el-upload-list__item-status-label {
  1368. display: inline-flex !important;
  1369. opacity: 1 !important;
  1370. }
  1371. .el-upload-list__item.is-success:focus .el-upload-list__item-status-label {
  1372. display: inline-flex !important;
  1373. opacity: 1 !important;
  1374. }
  1375. .el-icon--close-tip {
  1376. display: none !important;
  1377. }
  1378. }
  1379. </style>