friendCoupon.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <template>
  2. <div class="table-box button-table friend-coupon-container">
  3. <ProTable
  4. ref="proTable"
  5. :columns="columns"
  6. :request-api="getTableList"
  7. :init-param="initParam"
  8. :data-callback="dataCallback"
  9. :key="activeName"
  10. >
  11. <!-- 表格 header 按钮 -->
  12. <template #tableHeader="scope">
  13. <div class="table-header-content">
  14. <!-- <div class="header-button">
  15. <el-button type="primary" @click="openGiftDialog"> 赠送好友优惠券 </el-button>
  16. </div> -->
  17. <el-tabs v-model="activeName" class="header-tabs" @tab-click="handleTabClick">
  18. <el-tab-pane label="好友赠我" name="friendMessage" />
  19. <el-tab-pane label="我赠好友" name="myGift" />
  20. </el-tabs>
  21. </div>
  22. </template>
  23. <!-- 表格操作 -->
  24. <template #operation="scope">
  25. <el-button link type="primary" @click="viewDetail(scope.row)"> 查看详情 </el-button>
  26. <!-- <el-button v-if="activeName === 'myGift'" link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button> -->
  27. </template>
  28. </ProTable>
  29. <!-- 赠送好友优惠券对话框 -->
  30. <el-dialog v-model="giftDialogVisible" title="赠送好友优惠券" width="600px" @close="closeGiftDialog">
  31. <el-form ref="giftFormRef" :model="giftFormData" :rules="giftRules" label-width="120px">
  32. <el-form-item label="选择好友" prop="friendId">
  33. <el-select v-model="giftFormData.friendId" placeholder="请选择好友" style="width: 100%" :loading="friendListLoading">
  34. <el-option v-for="friend in friendList" :key="friend.id" :label="friend.name" :value="friend.id" />
  35. </el-select>
  36. </el-form-item>
  37. <el-form-item label="选择优惠券" prop="couponId">
  38. <el-select
  39. v-model="giftFormData.couponId"
  40. placeholder="请选择优惠券"
  41. style="width: 100%"
  42. :loading="couponListLoading"
  43. @focus="loadCouponList"
  44. @change="handleCouponChange"
  45. >
  46. <el-option v-for="coupon in couponList" :key="coupon.id" :label="coupon.name" :value="coupon.id" />
  47. </el-select>
  48. <div v-if="giftFormData.couponId" class="coupon-info">
  49. <span>请输入赠送数量</span>
  50. </div>
  51. </el-form-item>
  52. <el-form-item v-if="giftFormData.couponId" label="赠送数量" prop="quantity">
  53. <el-input-number v-model="giftFormData.quantity" :min="1" :max="100" placeholder="请输入数量" style="width: 100%" />
  54. </el-form-item>
  55. </el-form>
  56. <template #footer>
  57. <div class="dialog-footer">
  58. <el-button @click="closeGiftDialog"> 取消 </el-button>
  59. <el-button type="primary" @click="handleGiftSubmit"> 确定 </el-button>
  60. </div>
  61. </template>
  62. </el-dialog>
  63. </div>
  64. </template>
  65. <script setup lang="tsx" name="friendCoupon">
  66. import { computed, onMounted, reactive, ref } from "vue";
  67. import { useRouter } from "vue-router";
  68. import type { FormInstance, FormRules } from "element-plus";
  69. import { ElMessage, ElMessageBox } from "element-plus";
  70. import ProTable from "@/components/ProTable/index.vue";
  71. import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
  72. import { localGet } from "@/utils";
  73. import { getFriendCouponList, getMutualAttention, getCouponList, setFriendCoupon } from "@/api/modules/newLoginApi";
  74. const router = useRouter();
  75. // 当前激活的标签页
  76. const activeName = ref("friendMessage");
  77. // ProTable 实例
  78. const proTable = ref<ProTableInstance>();
  79. // 赠送对话框
  80. const giftDialogVisible = ref(false);
  81. const giftFormRef = ref<FormInstance>();
  82. // 好友列表
  83. const friendList = ref<any[]>([]);
  84. const friendListLoading = ref(false);
  85. const friendListLoaded = ref(false); // 标记是否已加载
  86. // 优惠券列表
  87. const couponList = ref<any[]>([]);
  88. const couponListLoading = ref(false);
  89. const couponListLoaded = ref(false); // 标记是否已加载
  90. // 加载好友列表
  91. const loadFriendList = async () => {
  92. // 如果已经加载过,则不重复加载
  93. if (friendListLoaded.value || friendListLoading.value) {
  94. return;
  95. }
  96. friendListLoading.value = true;
  97. try {
  98. const res: any = await getMutualAttention({
  99. page: 1,
  100. size: 999, // 获取所有好友
  101. fansId: "store_" + localGet("geeker-user")?.userInfo?.phone
  102. });
  103. if (res.code === 200) {
  104. friendList.value = res.data.records.map((item: any) => ({
  105. id: item.id,
  106. name: item.username
  107. }));
  108. friendListLoaded.value = true;
  109. } else {
  110. // 如果接口失败,使用模拟数据
  111. friendList.value = [];
  112. friendListLoaded.value = true;
  113. }
  114. } catch (error) {
  115. console.error("加载好友列表失败:", error);
  116. // 使用模拟数据作为备用
  117. friendList.value = [];
  118. friendListLoaded.value = true;
  119. } finally {
  120. friendListLoading.value = false;
  121. }
  122. };
  123. // 加载优惠券列表
  124. const loadCouponList = async () => {
  125. // 如果已经加载过,则不重复加载
  126. if (couponListLoaded.value || couponListLoading.value) {
  127. return;
  128. }
  129. couponListLoading.value = true;
  130. try {
  131. const res: any = await getCouponList({
  132. storeId: localGet("createdId"),
  133. status: 0
  134. });
  135. if (res.code === 200) {
  136. couponList.value = res.data.map((item: any) => ({
  137. id: item.id,
  138. name: item.name
  139. }));
  140. couponListLoaded.value = true;
  141. } else {
  142. // 如果接口失败,使用模拟数据
  143. couponList.value = [];
  144. couponListLoaded.value = true;
  145. }
  146. } catch (error) {
  147. console.error("加载优惠券列表失败:", error);
  148. // 使用模拟数据作为备用
  149. couponList.value = [];
  150. couponListLoaded.value = true;
  151. } finally {
  152. couponListLoading.value = false;
  153. }
  154. };
  155. // 赠送表单数据
  156. const giftFormData = reactive({
  157. friendId: "",
  158. couponId: "",
  159. quantity: 1
  160. });
  161. // 表单验证规则
  162. const giftRules = reactive<FormRules>({
  163. friendId: [{ required: true, message: "请选择好友", trigger: "change" }],
  164. couponId: [{ required: true, message: "请选择优惠券", trigger: "change" }],
  165. quantity: [{ required: true, message: "请输入赠送数量", trigger: "blur" }]
  166. });
  167. // 好友赠我表格列配置
  168. const friendMessageColumns = reactive<ColumnProps<any>[]>([
  169. {
  170. prop: "storeName",
  171. label: "店铺名称",
  172. search: {
  173. el: "input",
  174. props: { placeholder: "请输入" }
  175. }
  176. },
  177. {
  178. prop: "couponType",
  179. label: "类型",
  180. search: {
  181. el: "select",
  182. props: { placeholder: "请选择" }
  183. },
  184. enum: [
  185. { label: "满减券", value: "1" },
  186. { label: "折扣券", value: "2" }
  187. ],
  188. render: (scope: any) => {
  189. const typeMap: Record<string, string> = { "1": "满减券", "2": "折扣券" };
  190. const val = scope.row.couponType ?? scope.row.type;
  191. return typeMap[String(val)] ?? scope.row.couponTypeName ?? "--";
  192. }
  193. },
  194. {
  195. prop: "couponName",
  196. label: "优惠券名称",
  197. render: (scope: any) => {
  198. const r = scope.row;
  199. return r.couponName ?? r.name ?? r.couponTitle ?? "--";
  200. }
  201. },
  202. {
  203. prop: "couponNum",
  204. label: "数量"
  205. },
  206. {
  207. prop: "longTermValid",
  208. label: "有效期",
  209. render: (scope: any) => {
  210. const r = scope.row;
  211. const lt = r.longTermValid;
  212. if (lt === 1 || lt === "1" || lt === true) return "长期有效";
  213. const ed = r.expirationDate;
  214. if (ed === null || ed === undefined || ed === "") return "--";
  215. return `${ed}天`;
  216. }
  217. },
  218. { prop: "operation", label: "操作", fixed: "right", width: 200 }
  219. ]);
  220. // 我赠好友表格列配置(与好友赠我保持一致)
  221. const myGiftColumns = reactive<ColumnProps<any>[]>([
  222. {
  223. prop: "storeName",
  224. label: "店铺名称",
  225. search: {
  226. el: "input",
  227. props: { placeholder: "请输入" }
  228. }
  229. },
  230. {
  231. prop: "couponType",
  232. label: "类型",
  233. search: {
  234. el: "select",
  235. props: { placeholder: "请选择" }
  236. },
  237. enum: [
  238. { label: "满减券", value: "1" },
  239. { label: "折扣券", value: "2" }
  240. ],
  241. render: (scope: any) => {
  242. const typeMap: Record<string, string> = { "1": "满减券", "2": "折扣券" };
  243. const val = scope.row.couponType ?? scope.row.type;
  244. return typeMap[String(val)] ?? scope.row.couponTypeName ?? "--";
  245. }
  246. },
  247. {
  248. prop: "couponName",
  249. label: "优惠券名称",
  250. render: (scope: any) => {
  251. const r = scope.row;
  252. return r.couponName ?? r.name ?? r.couponTitle ?? "--";
  253. }
  254. },
  255. {
  256. prop: "couponNum",
  257. label: "数量"
  258. },
  259. {
  260. prop: "longTermValid",
  261. label: "有效期",
  262. render: (scope: any) => {
  263. const r = scope.row;
  264. const lt = r.longTermValid;
  265. if (lt === 1 || lt === "1" || lt === true) return "长期有效";
  266. const ed = r.expirationDate;
  267. if (ed === null || ed === undefined || ed === "") return "--";
  268. return `${ed}天`;
  269. }
  270. },
  271. { prop: "operation", label: "操作", fixed: "right", width: 200 }
  272. ]);
  273. // 根据当前选中的tab动态返回列配置
  274. const columns = computed(() => {
  275. return activeName.value === "friendMessage" ? friendMessageColumns : myGiftColumns;
  276. });
  277. // 初始化请求参数:列表接口格式 storeUserId、storeName、type、queryType(queryType 1=好友赠我 2=我赠好友)
  278. const initParam = reactive<Record<string, any>>({
  279. storeUserId: localGet("createdId") ?? "",
  280. queryType: 1
  281. });
  282. // Tab 切换处理:queryType 1=好友赠我,2=我赠好友
  283. const handleTabClick = () => {
  284. initParam.queryType = activeName.value === "friendMessage" ? 1 : 2;
  285. proTable.value?.getTableList();
  286. };
  287. // dataCallback:兼容后端返回格式——data 直接为数组 或 { records/list, total },统一为 { list, total }
  288. const dataCallback = (data: any) => {
  289. if (!data) return { list: [], total: 0 };
  290. if (Array.isArray(data)) {
  291. return { list: data, total: data.length };
  292. }
  293. const list = data.records ?? data.list ?? [];
  294. const total = data.total ?? list.length;
  295. return { list, total };
  296. };
  297. // 获取表格列表:接口参数 storeUserId、storeName、couponType、queryType(1=好友赠我 2=我赠好友)、分页
  298. const getTableList = (params: any) => {
  299. let storeUserId = localGet("geeker-user")?.userInfo?.id;
  300. const newParams: any = {
  301. storeUserId: storeUserId,
  302. storeName: params.storeName ?? "",
  303. type: 1,
  304. queryType: activeName.value === "friendMessage" ? 1 : 2,
  305. page: params.pageNum ?? 1,
  306. size: params.pageSize ?? 10
  307. };
  308. // 优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(搜索框可能传字符串 '1'/'2')
  309. const couponType = params.couponType != null && params.couponType !== "" ? Number(params.couponType) : undefined;
  310. if (couponType === 1 || couponType === 2) {
  311. newParams.couponType = couponType;
  312. }
  313. return getFriendCouponList(newParams);
  314. };
  315. // 打开赠送对话框
  316. const openGiftDialog = () => {
  317. giftDialogVisible.value = true;
  318. loadFriendList();
  319. loadCouponList();
  320. // 点击下拉框时才会加载数据(通过 @focus 事件触发)
  321. };
  322. // 关闭赠送对话框
  323. const closeGiftDialog = () => {
  324. giftDialogVisible.value = false;
  325. giftFormRef.value?.resetFields();
  326. Object.assign(giftFormData, {
  327. friendId: "",
  328. couponId: "",
  329. quantity: 1
  330. });
  331. // 重置加载状态,下次打开时重新加载
  332. friendListLoaded.value = false;
  333. couponListLoaded.value = false;
  334. };
  335. // 优惠券改变时
  336. const handleCouponChange = (val: string) => {
  337. giftFormData.quantity = 1;
  338. };
  339. // 提交赠送
  340. const handleGiftSubmit = async () => {
  341. if (!giftFormRef.value) return;
  342. await giftFormRef.value.validate(async (valid: boolean) => {
  343. if (valid) {
  344. try {
  345. // 调用赠送接口
  346. const params = {
  347. couponIds: [
  348. {
  349. couponId: giftFormData.couponId,
  350. singleQty: giftFormData.quantity
  351. }
  352. ],
  353. friendStoreUserId: String(giftFormData.friendId)
  354. };
  355. const res: any = await setFriendCoupon(params);
  356. if (res && res.code === 200) {
  357. ElMessage.success("赠送成功");
  358. closeGiftDialog();
  359. proTable.value?.getTableList();
  360. } else {
  361. ElMessage.error(res?.msg || "赠送失败");
  362. }
  363. } catch (error: any) {
  364. console.error("赠送失败:", error);
  365. ElMessage.error(error?.message || "赠送失败");
  366. }
  367. }
  368. });
  369. };
  370. // 查看详情:整行写入 sessionStorage,详情页直接展示,不调接口
  371. const FRIEND_COUPON_DETAIL_ROW_KEY = "friendCoupon_detail_row";
  372. const viewDetail = (row: any) => {
  373. try {
  374. sessionStorage.setItem(FRIEND_COUPON_DETAIL_ROW_KEY, JSON.stringify(row ?? {}));
  375. } catch (e) {
  376. console.error(e);
  377. ElMessage.warning("无法缓存行数据,请重试");
  378. return;
  379. }
  380. const query: Record<string, string> = { type: String(activeName.value || "") };
  381. if (row?.voucherId != null && row.voucherId !== "") {
  382. query.voucherId = String(row.voucherId);
  383. } else if (row?.couponId != null && row.couponId !== "") {
  384. query.couponId = String(row.couponId);
  385. }
  386. if (row?.storeId != null && row.storeId !== "") query.storeId = String(row.storeId);
  387. router.push({
  388. path: "/dynamicManagement/friendCouponDetail",
  389. query
  390. });
  391. };
  392. // 删除行数据
  393. const deleteRow = (row: any) => {
  394. ElMessageBox.confirm("确定要删除这条赠送记录吗?", "提示", {
  395. confirmButtonText: "确定",
  396. cancelButtonText: "取消",
  397. type: "warning"
  398. })
  399. .then(async () => {
  400. try {
  401. // TODO: 集成真实接口时,取消下面的注释
  402. // await deleteFriendCoupon({ id: row.id });
  403. ElMessage.success("删除成功");
  404. proTable.value?.getTableList();
  405. } catch (error) {
  406. ElMessage.error("删除失败");
  407. }
  408. })
  409. .catch(() => {
  410. // 用户取消删除
  411. });
  412. };
  413. // 页面加载时触发查询
  414. onMounted(() => {
  415. proTable.value?.getTableList();
  416. });
  417. </script>
  418. <style lang="scss" scoped>
  419. .friend-coupon-container {
  420. .table-header-content {
  421. display: flex;
  422. flex-direction: column;
  423. gap: 16px;
  424. .header-tabs {
  425. :deep(.el-tabs__nav-wrap::after) {
  426. height: 0;
  427. }
  428. }
  429. .header-button {
  430. display: flex;
  431. justify-content: flex-start;
  432. }
  433. }
  434. .coupon-info {
  435. margin-top: 8px;
  436. font-size: 12px;
  437. color: #909399;
  438. }
  439. .dialog-footer {
  440. display: flex;
  441. gap: 10px;
  442. justify-content: flex-end;
  443. }
  444. }
  445. </style>