friendRelation.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. <template>
  2. <div class="table-box button-table friend-relation-container">
  3. <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
  4. <!-- 表格 header 按钮 -->
  5. <template #tableHeader="scope">
  6. <div class="header-button">
  7. <el-button type="primary" @click="openAddDialog"> 添加活动 </el-button>
  8. </div>
  9. </template>
  10. <!-- 状态列 -->
  11. <template #status="scope">
  12. <el-tag :type="getStatusType(scope.row.status)">
  13. {{ getStatusText(scope.row.status) }}
  14. </el-tag>
  15. </template>
  16. <!-- 表格操作 -->
  17. <template #operation="scope">
  18. <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
  19. <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
  20. </template>
  21. </ProTable>
  22. <!-- 添加/编辑活动对话框 -->
  23. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" @close="closeDialog">
  24. <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px">
  25. <el-form-item label="活动名称" prop="acName">
  26. <el-input v-model="formData.acName" placeholder="请输入" clearable />
  27. </el-form-item>
  28. <el-form-item label="赠送类型">
  29. <el-radio-group v-model="formData.distributeType" @change="onDistributeTypeChange">
  30. <el-radio :label="1"> 满减券 </el-radio>
  31. <el-radio :label="2"> 折扣券 </el-radio>
  32. </el-radio-group>
  33. </el-form-item>
  34. <el-form-item label="消费门槛金额(¥)">
  35. <div style="display: flex; gap: 10px; align-items: center; width: 100%">
  36. <el-form-item prop="moneyLow" style="flex: 1; margin-bottom: 0">
  37. <el-input
  38. v-model="formData.moneyLow"
  39. type="number"
  40. :min="0"
  41. step="0.01"
  42. placeholder="0.00"
  43. clearable
  44. @input="handleMoneyInput('moneyLow')"
  45. >
  46. <template #prefix> ¥ </template>
  47. </el-input>
  48. </el-form-item>
  49. <span>~</span>
  50. <el-form-item prop="moneyHigh" style="flex: 1; margin-bottom: 0">
  51. <el-input
  52. v-model="formData.moneyHigh"
  53. type="number"
  54. :min="0"
  55. step="0.01"
  56. placeholder="0.00"
  57. clearable
  58. @input="handleMoneyInput('moneyHigh')"
  59. >
  60. <template #prefix> ¥ </template>
  61. </el-input>
  62. </el-form-item>
  63. </div>
  64. </el-form-item>
  65. <el-form-item label="来源商家及优惠券">
  66. <div class="merchant-coupon-list">
  67. <div v-for="(item, index) in formData.coupons" :key="index" class="merchant-coupon-item">
  68. <el-select
  69. v-model="item.merchant"
  70. placeholder="选择商家"
  71. style="width: 200px; margin-right: 10px"
  72. filterable
  73. @change="handleMerchantChange(index)"
  74. >
  75. <el-option
  76. v-for="(m, idx) in effectiveMerchantOptions"
  77. :key="m.label ? `${String(m.value)}_${m.label}_${idx}` : String(m.value)"
  78. :label="m.label"
  79. :value="m.value"
  80. />
  81. </el-select>
  82. <el-select
  83. v-model="item.coupon"
  84. :placeholder="formData.distributeType === 2 ? '选择折扣券' : '选择满减券'"
  85. style="width: 200px; margin-right: 10px"
  86. filterable
  87. @change="handleCouponChange(index)"
  88. >
  89. <el-option
  90. v-for="c in getCouponOptions(formData.distributeType)"
  91. :key="c.value"
  92. :label="c.label"
  93. :value="c.value"
  94. />
  95. </el-select>
  96. <span v-if="item.coupon" class="remaining-text">剩余{{ item.remaining }}张</span>
  97. <el-button type="danger" link @click="removeMerchantCoupon(index)" v-if="formData.coupons.length > 1">
  98. 删除
  99. </el-button>
  100. </div>
  101. <el-button type="primary" link @click="addMerchantCoupon" style="margin-top: 10px">
  102. <el-icon><Plus /></el-icon>
  103. 添加商家优惠券
  104. </el-button>
  105. </div>
  106. </el-form-item>
  107. </el-form>
  108. <template #footer>
  109. <div class="dialog-footer">
  110. <el-button @click="closeDialog"> 取消 </el-button>
  111. <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
  112. </div>
  113. </template>
  114. </el-dialog>
  115. </div>
  116. </template>
  117. <script setup lang="tsx" name="friendRelation">
  118. import { computed, onMounted, reactive, ref } from "vue";
  119. import type { FormInstance, FormRules } from "element-plus";
  120. import { ElMessage, ElMessageBox } from "element-plus";
  121. import { Plus } from "@element-plus/icons-vue";
  122. import ProTable from "@/components/ProTable/index.vue";
  123. import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
  124. import { localGet } from "@/utils";
  125. import {
  126. getRuleList,
  127. saveFriendCouponRule,
  128. getMutualAttention,
  129. delFriendCouponRule,
  130. getRuleById,
  131. getVoucherList,
  132. getIssueCouponList
  133. } from "@/api/modules/newLoginApi";
  134. // ProTable 实例
  135. const proTable = ref<ProTableInstance>();
  136. // 对话框相关
  137. const dialogVisible = ref(false);
  138. const dialogTitle = computed(() => (isEdit.value ? "编辑活动" : "添加活动"));
  139. const isEdit = ref(false);
  140. const currentEditId = ref("");
  141. // 表单引用
  142. const formRef = ref<FormInstance>();
  143. // 商家列表(互相关注接口,仅显示 phoneId 带 store 的商家)
  144. const merchantList = ref<Array<{ value: string | number; label: string; raw?: any }>>([]);
  145. const merchantListRaw = ref<any[]>([]);
  146. // 优惠券/折扣券选项缓存 key: '1-couponList' | '2-couponList'
  147. const couponOptionsMap = ref<Record<string, Array<{ value: string; label: string }>>>({});
  148. const couponDataMap = ref<Record<string, any[]>>({});
  149. const normalizeCouponValue = (v: any) => (v != null && v !== "" ? String(v) : "");
  150. // 编辑时合并详情中的 storeName,使商家下拉能正确显示;按 label 去重,避免同一商家出现多次
  151. const effectiveMerchantOptions = computed(() => {
  152. const base = merchantList.value || [];
  153. const fromDetail = (formData.coupons || [])
  154. .filter((c: any) => c.merchant != null && c.merchant !== "" && c.storeName)
  155. .map((c: any) => ({ value: c.merchant, label: c.storeName }));
  156. const seenValue = new Set(base.map((o: any) => o.value));
  157. const extra = fromDetail.filter((o: any) => {
  158. if (seenValue.has(o.value)) return false;
  159. seenValue.add(o.value);
  160. return true;
  161. });
  162. const merged = [...base, ...extra];
  163. // 按 label(店名)去重:同一店名只保留一项,优先保留在 form 中已选中的 value,避免下拉重复显示
  164. const usedValues = new Set((formData.coupons || []).map((c: any) => c.merchant).filter((v: any) => v != null && v !== ""));
  165. const byLabel = new Map<string, { value: string | number; label: string }>();
  166. for (const o of merged) {
  167. const label = o.label || "";
  168. const existing = byLabel.get(label);
  169. if (!existing) {
  170. byLabel.set(label, o);
  171. } else if (usedValues.has(o.value) && !usedValues.has(existing.value)) {
  172. byLabel.set(label, o);
  173. }
  174. }
  175. return Array.from(byLabel.values());
  176. });
  177. const getCouponOptions = (distributeType: number) => {
  178. const key = distributeType === 2 ? "2-couponList" : "1-couponList";
  179. return couponOptionsMap.value[key] || [];
  180. };
  181. // 表单数据
  182. const formData = reactive({
  183. acName: "",
  184. distributeType: 1 as 1 | 2, // 1-满减券 2-折扣券
  185. moneyLow: "" as string | number,
  186. moneyHigh: "" as string | number,
  187. coupons: [] as Array<{
  188. merchant: string | number | null;
  189. coupon: string | null;
  190. remaining: number;
  191. storeName?: string;
  192. }>
  193. });
  194. // 金额验证器
  195. const validateMoneyLow = (rule: any, value: any, callback: any) => {
  196. if (value === "" || value === null || value === undefined) {
  197. callback(new Error("请输入最低消费金额"));
  198. return;
  199. }
  200. const numValue = Number(value);
  201. if (isNaN(numValue)) {
  202. callback(new Error("请输入有效的金额"));
  203. return;
  204. }
  205. if (numValue < 0) {
  206. callback(new Error("金额不能为负数"));
  207. return;
  208. }
  209. // 验证小数位数不超过2位
  210. const decimalPart = String(value).split(".")[1];
  211. if (decimalPart && decimalPart.length > 2) {
  212. callback(new Error("最多支持2位小数"));
  213. return;
  214. }
  215. // 如果最高金额已填写,验证最低金额不能大于最高金额
  216. if (formData.moneyHigh !== "" && formData.moneyHigh !== null && formData.moneyHigh !== undefined) {
  217. const highValue = Number(formData.moneyHigh);
  218. if (!isNaN(highValue) && numValue > highValue) {
  219. callback(new Error("最低金额不能大于最高金额"));
  220. return;
  221. }
  222. }
  223. callback();
  224. };
  225. const validateMoneyHigh = (rule: any, value: any, callback: any) => {
  226. if (value === "" || value === null || value === undefined) {
  227. callback(new Error("请输入最高消费金额"));
  228. return;
  229. }
  230. const numValue = Number(value);
  231. if (isNaN(numValue)) {
  232. callback(new Error("请输入有效的金额"));
  233. return;
  234. }
  235. if (numValue < 0) {
  236. callback(new Error("金额不能为负数"));
  237. return;
  238. }
  239. // 验证小数位数不超过2位
  240. const decimalPart = String(value).split(".")[1];
  241. if (decimalPart && decimalPart.length > 2) {
  242. callback(new Error("最多支持2位小数"));
  243. return;
  244. }
  245. // 如果最低金额已填写,验证最高金额不能小于最低金额
  246. if (formData.moneyLow !== "" && formData.moneyLow !== null && formData.moneyLow !== undefined) {
  247. const lowValue = Number(formData.moneyLow);
  248. if (!isNaN(lowValue) && numValue < lowValue) {
  249. callback(new Error("最高金额不能小于最低金额"));
  250. return;
  251. }
  252. }
  253. callback();
  254. };
  255. // 表单验证规则
  256. const formRules = reactive<FormRules>({
  257. acName: [{ required: true, message: "请输入活动名称", trigger: "blur" }],
  258. moneyLow: [{ required: true, validator: validateMoneyLow, trigger: "blur" }],
  259. moneyHigh: [{ required: true, validator: validateMoneyHigh, trigger: "blur" }]
  260. });
  261. // 表格列配置
  262. const columns = reactive<ColumnProps<any>[]>([
  263. {
  264. prop: "acName",
  265. label: "活动名称",
  266. search: { el: "input", props: { placeholder: "请输入活动名称" } }
  267. },
  268. {
  269. prop: "endDate",
  270. label: "有效期至"
  271. },
  272. {
  273. prop: "relationType",
  274. label: "消费门槛",
  275. render: (scope: any) => {
  276. return scope.row.moneyLow + "元" + "~" + scope.row.moneyHigh + "元";
  277. }
  278. },
  279. {
  280. prop: "status",
  281. label: "状态",
  282. enum: [
  283. { label: "启用", value: 0 },
  284. { label: "禁用", value: 1 }
  285. ],
  286. search: { el: "select", props: { placeholder: "请选择状态" } },
  287. fieldNames: { label: "label", value: "value" },
  288. render: (scope: any) => {
  289. return scope.row.status == 0 ? "启用" : "禁用";
  290. }
  291. },
  292. { prop: "operation", label: "操作", fixed: "right", width: 250 }
  293. ]);
  294. // 初始化请求参数
  295. const initParam = reactive({
  296. storeId: localGet("createdId") || ""
  297. });
  298. // 数据回调处理:兼容接口返回数组或 { records/list, total } 格式,确保有数据时分页显示正确总数(iOS 上 total 需为明确数字)
  299. const dataCallback = (data: any) => {
  300. const list = Array.isArray(data) ? data : (data?.records ?? data?.list ?? []);
  301. const totalNum =
  302. typeof data?.total === "number" && Number.isFinite(data.total) ? data.total : Array.isArray(list) ? list.length : 0;
  303. const total = Math.max(0, Math.floor(Number(totalNum)) || 0);
  304. return {
  305. list,
  306. total
  307. };
  308. };
  309. // 获取表格列表
  310. const getTableList = (params: any) => {
  311. return getRuleList(params);
  312. };
  313. // 获取状态文本
  314. const getStatusText = (status: number) => {
  315. const statusMap: Record<number, string> = {
  316. 0: "待同意",
  317. 1: "已同意",
  318. 2: "已拒绝"
  319. };
  320. return statusMap[status] || "--";
  321. };
  322. // 获取状态类型
  323. const getStatusType = (status: number): "success" | "warning" | "info" | "danger" => {
  324. const typeMap: Record<number, "success" | "warning" | "info" | "danger"> = {
  325. 0: "warning",
  326. 1: "success",
  327. 2: "info"
  328. };
  329. return typeMap[status] || "info";
  330. };
  331. // 获取商家列表(互相关注接口,仅显示 phoneId 带 store 的商家)- 与 group_merchant eachOtherInterest 参数一致
  332. const loadAddActivityMerchants = async () => {
  333. const phone = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
  334. try {
  335. const res: any = await getMutualAttention({
  336. page: 1,
  337. size: 1000,
  338. fansId: `store_${phone}`
  339. });
  340. const data = res?.data || res;
  341. const records = data?.records || data?.list || (Array.isArray(data) ? data : []);
  342. const list = Array.isArray(records) ? records : [];
  343. const merchantListFiltered = list.filter((item: any) => {
  344. const pid = String(item.phoneId ?? item.id ?? "");
  345. return pid.includes("store");
  346. });
  347. merchantListRaw.value = merchantListFiltered;
  348. // 按 value(id/storeId/phoneId)去重,避免同一商家在接口中多次返回导致下拉重复
  349. const seen = new Set<string | number>();
  350. merchantList.value = merchantListFiltered
  351. .map((item: any) => ({
  352. value: item.id ?? item.storeId ?? item.phoneId,
  353. label: item.storeName ?? item.name ?? "",
  354. raw: item
  355. }))
  356. .filter((o: any) => {
  357. if (seen.has(o.value)) return false;
  358. seen.add(o.value);
  359. return true;
  360. });
  361. } catch (error) {
  362. console.error("获取商家列表失败", error);
  363. merchantList.value = [];
  364. merchantListRaw.value = [];
  365. }
  366. };
  367. // 打开添加对话框
  368. const openAddDialog = async () => {
  369. isEdit.value = false;
  370. currentEditId.value = "";
  371. await loadAddActivityMerchants();
  372. couponOptionsMap.value = {};
  373. couponDataMap.value = {};
  374. formData.distributeType = 1;
  375. formData.coupons = [{ merchant: null, coupon: null, remaining: 0 }];
  376. formData.acName = "";
  377. formData.moneyLow = "";
  378. formData.moneyHigh = "";
  379. dialogVisible.value = true;
  380. };
  381. // 处理金额输入
  382. const handleMoneyInput = (field: "moneyLow" | "moneyHigh") => {
  383. // 当一个金额字段改变时,触发另一个字段的验证
  384. if (formRef.value) {
  385. const otherField = field === "moneyLow" ? "moneyHigh" : "moneyLow";
  386. // 如果另一个字段有值,触发其验证
  387. if (formData[otherField] !== "" && formData[otherField] !== null && formData[otherField] !== undefined) {
  388. formRef.value.validateField(otherField, () => {});
  389. }
  390. }
  391. };
  392. // 关闭对话框
  393. const closeDialog = () => {
  394. dialogVisible.value = false;
  395. formRef.value?.resetFields();
  396. Object.assign(formData, {
  397. acName: "",
  398. distributeType: 1,
  399. moneyLow: "",
  400. moneyHigh: "",
  401. coupons: []
  402. });
  403. currentEditId.value = "";
  404. };
  405. // 添加商家优惠券
  406. const addMerchantCoupon = () => {
  407. formData.coupons.push({
  408. merchant: null,
  409. coupon: null,
  410. remaining: 0
  411. });
  412. };
  413. // 移除商家优惠券
  414. const removeMerchantCoupon = (index: number) => {
  415. formData.coupons.splice(index, 1);
  416. };
  417. // 赠送类型切换:清空选项缓存并重置行内选择(与 group_merchant setDistributeType 一致)
  418. const onDistributeTypeChange = () => {
  419. couponOptionsMap.value = {};
  420. couponDataMap.value = {};
  421. formData.coupons = formData.coupons.map(() => ({
  422. merchant: null,
  423. coupon: null,
  424. remaining: 0
  425. }));
  426. if (formData.coupons.length !== 1) formData.coupons = [{ merchant: null, coupon: null, remaining: 0 }];
  427. loadAddActivityMerchants();
  428. };
  429. // 加载当前类型下的优惠券/折扣券选项
  430. const loadCouponOptionsByType = async (distributeType: number) => {
  431. const key = distributeType === 2 ? "2-couponList" : "1-couponList";
  432. if (couponOptionsMap.value[key]?.length) return;
  433. const storeId = localGet("createdId") || "";
  434. try {
  435. const res: any = await getIssueCouponList({
  436. storeId,
  437. tab: 1,
  438. size: 100,
  439. page: 1,
  440. couponName: "",
  441. couponsFromType: 1,
  442. couponType: formData.distributeType
  443. });
  444. const records = res?.data?.records ?? res?.data ?? [];
  445. const list = Array.isArray(records) ? records : [];
  446. couponDataMap.value[key] = list;
  447. couponOptionsMap.value[key] = list.map((item: any) => ({
  448. value: String(item.id ?? item.couponId ?? ""),
  449. label: item.couponName ?? item.name ?? ""
  450. }));
  451. } catch (e) {
  452. couponDataMap.value[key] = [];
  453. couponOptionsMap.value[key] = [];
  454. }
  455. };
  456. // 商家选择改变
  457. const handleMerchantChange = async (index: number) => {
  458. const item = formData.coupons[index];
  459. item.coupon = null;
  460. item.remaining = 0;
  461. if (item.merchant) {
  462. const raw = merchantListRaw.value.find(
  463. (m: any) => (m.id ?? m.friendStoreUserId) === item.merchant || (m.storeId ?? m.phoneId) === item.merchant
  464. );
  465. if (raw) item.remaining = raw.singleQty ?? raw.couponNum ?? 0;
  466. await loadCouponOptionsByType(formData.distributeType);
  467. }
  468. };
  469. // 优惠券/折扣券选择改变
  470. const handleCouponChange = (index: number) => {
  471. const item = formData.coupons[index];
  472. const key = formData.distributeType === 2 ? "2-couponList" : "1-couponList";
  473. const list = couponDataMap.value[key] || [];
  474. const idKey = formData.distributeType === 2 ? "voucherId" : "couponId";
  475. if (item.coupon && list.length) {
  476. const found = list.find((c: any) => String(c[idKey] ?? c.couponId ?? c.id ?? "") === String(item.coupon));
  477. if (found) item.remaining = found.singleQty ?? found.couponNum ?? 0;
  478. }
  479. };
  480. // 编辑行数据(与 group_merchant editActivity 逻辑及 getRuleById 回显一致)
  481. const editRow = async (row: any) => {
  482. isEdit.value = true;
  483. currentEditId.value = row.id;
  484. const id = row.id;
  485. if (!id) return;
  486. await loadAddActivityMerchants();
  487. const type = row.type === 2 ? 2 : 1;
  488. formData.distributeType = type;
  489. couponOptionsMap.value = {};
  490. couponDataMap.value = {};
  491. try {
  492. const res: any = await getRuleById({ id });
  493. if (res.code != 200 || !res.data) {
  494. ElMessage.error(res.msg || "获取活动详情失败");
  495. return;
  496. }
  497. const d = res.data;
  498. formData.acName = d.acName;
  499. formData.moneyLow = d.moneyLow;
  500. formData.moneyHigh = d.moneyHigh;
  501. formData.distributeType = d.couponType;
  502. formData.coupons = (d.lifeDiscountCouponFriendRuleDetailVos || []).map((vo: any) => ({
  503. merchant: vo.friendStoreUserId ?? null,
  504. coupon: normalizeCouponValue(vo.couponId ?? vo.voucherId),
  505. remaining: vo.singleQty ?? vo.couponNum ?? 0,
  506. storeName: vo.storeName ?? ""
  507. }));
  508. if (formData.coupons.length === 0) {
  509. formData.coupons = [{ merchant: null, coupon: null, remaining: 0 }];
  510. }
  511. await loadCouponOptionsByType(formData.distributeType);
  512. // 编辑回显:从已加载的券列表中按券 id 同步剩余张数,避免详情接口未返回 singleQty/couponNum 时一直显示 0
  513. const key = formData.distributeType === 2 ? "2-couponList" : "1-couponList";
  514. const list = couponDataMap.value[key] || [];
  515. const idKey = formData.distributeType === 2 ? "voucherId" : "couponId";
  516. formData.coupons.forEach((row: any) => {
  517. if (!row.coupon) return;
  518. const found = list.find((c: any) => String(c[idKey] ?? c.couponId ?? c.id ?? "") === String(row.coupon));
  519. if (found) row.remaining = found.singleQty ?? found.couponNum ?? 0;
  520. });
  521. dialogVisible.value = true;
  522. } catch (error: any) {
  523. ElMessage.error(error?.msg || "获取活动详情失败");
  524. }
  525. };
  526. // 删除行数据
  527. const deleteRow = (row: any) => {
  528. ElMessageBox.confirm("确定要删除这个活动吗?", "提示", {
  529. confirmButtonText: "确定",
  530. cancelButtonText: "取消",
  531. type: "warning"
  532. })
  533. .then(async () => {
  534. try {
  535. const res: any = await delFriendCouponRule({ id: row.id });
  536. if (res.code == 200) {
  537. ElMessage.success("删除成功");
  538. proTable.value?.getTableList();
  539. } else {
  540. ElMessage.error(res.msg || "删除失败");
  541. }
  542. } catch (error: any) {
  543. ElMessage.error(error?.msg || "删除失败");
  544. }
  545. })
  546. .catch(() => {
  547. // 用户取消删除
  548. });
  549. };
  550. // 提交表单(与 group_merchant saveActivity 请求参数一致)
  551. const handleSubmit = async () => {
  552. if (!formRef.value) return;
  553. await formRef.value.validate(async (valid: boolean) => {
  554. if (!valid) return;
  555. if (!formData.coupons.length) {
  556. ElMessage.warning("请至少添加一个商家优惠券");
  557. return;
  558. }
  559. const hasEmpty = formData.coupons.some((item: any) => !item.merchant || !item.coupon);
  560. if (hasEmpty) {
  561. ElMessage.warning("请完善所有商家优惠券信息");
  562. return;
  563. }
  564. const isVoucher = formData.distributeType === 2;
  565. const details = formData.coupons.map((item: any) =>
  566. isVoucher
  567. ? { voucherId: item.coupon, friendStoreUserId: item.merchant }
  568. : { couponId: item.coupon, friendStoreUserId: item.merchant }
  569. );
  570. const requestData: any = {
  571. storeId: localGet("createdId") || "",
  572. acName: formData.acName.trim(),
  573. couponType: formData.distributeType,
  574. moneyHigh: Number(formData.moneyHigh),
  575. moneyLow: Number(formData.moneyLow),
  576. details
  577. };
  578. if (isEdit.value && currentEditId.value) {
  579. requestData.id = currentEditId.value;
  580. }
  581. try {
  582. const res: any = await saveFriendCouponRule(requestData);
  583. if (res.code == 200) {
  584. ElMessage.success(isEdit.value ? "编辑成功" : "添加成功");
  585. closeDialog();
  586. proTable.value?.getTableList();
  587. } else {
  588. ElMessage.error(res.msg || "操作失败");
  589. }
  590. } catch (error: any) {
  591. ElMessage.error(error?.msg || (isEdit.value ? "编辑失败" : "添加失败"));
  592. }
  593. });
  594. };
  595. // 页面加载时触发查询
  596. onMounted(() => {
  597. proTable.value?.getTableList();
  598. });
  599. </script>
  600. <style lang="scss" scoped>
  601. .friend-relation-container {
  602. .header-button {
  603. margin-bottom: 16px;
  604. }
  605. .merchant-coupon-list {
  606. width: 100%;
  607. .merchant-coupon-item {
  608. display: flex;
  609. align-items: center;
  610. margin-bottom: 10px;
  611. }
  612. .remaining-text {
  613. margin-right: 10px;
  614. font-size: 13px;
  615. color: var(--el-text-color-regular);
  616. }
  617. }
  618. .dialog-footer {
  619. display: flex;
  620. gap: 10px;
  621. justify-content: flex-end;
  622. }
  623. }
  624. </style>