index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. <template>
  2. <div class="discount-code-page">
  3. <!-- 头部导航 -->
  4. <s-header :name="$t('discountCode.discountCode')" :noback="false" />
  5. <!-- 主要内容区域 -->
  6. <!-- 顶部操作栏 -->
  7. <div class="top-action-bar" :class="{ 'delete-mode': isDelete }">
  8. <div class="status-display">
  9. <template v-if="isDelete">
  10. <span>{{ $t("advertManage.advertRule.selected") }}</span>
  11. <span class="count-highlight">{{ selectTotals }}</span>
  12. <span>{{ $t("advertManage.advertRule.individual") }}</span>
  13. </template>
  14. <template v-else>
  15. <span>{{ $t("discountCode.total") }}</span>
  16. <span class="count-highlight">{{ discountCodeTotal }}</span>
  17. <span>{{ $t("discountCode.discountCodesInTotal") }}</span>
  18. </template>
  19. </div>
  20. <div class="action-buttons">
  21. <template v-if="isDelete">
  22. <van-button
  23. size="small"
  24. class="action-button"
  25. @click="cancelClk"
  26. >
  27. {{ $t("discountCode.cancel") }}
  28. </van-button>
  29. <van-button
  30. size="small"
  31. type="primary"
  32. class="action-button"
  33. @click="allClk"
  34. >
  35. {{ $t("discountCode.selectAll") }}
  36. </van-button>
  37. <van-button
  38. size="small"
  39. type="danger"
  40. class="action-button"
  41. @click="noticeClk"
  42. >
  43. {{ $t("discountCode.confirmDel") }}
  44. </van-button>
  45. </template>
  46. <template v-else>
  47. <van-button
  48. icon="search"
  49. size="small"
  50. plain
  51. class="action-button"
  52. @click="searchClick"
  53. />
  54. <van-button
  55. icon="plus"
  56. type="primary"
  57. size="small"
  58. class="action-button"
  59. @click="payCode"
  60. >
  61. {{ $t("discountCode.apply") }}
  62. </van-button>
  63. <van-button
  64. icon="delete"
  65. size="small"
  66. type="danger"
  67. class="action-button"
  68. @click="isDelete = true"
  69. >
  70. {{ $t("discountCode.delete") }}
  71. </van-button>
  72. <van-button
  73. icon="down"
  74. size="small"
  75. type="success"
  76. class="action-button"
  77. @click="clickExport"
  78. >
  79. {{ $t("discountCode.export") }}
  80. </van-button>
  81. </template>
  82. </div>
  83. </div>
  84. <!-- 标签切换区域 -->
  85. <div class="tab-section">
  86. <div class="tab-buttons">
  87. <van-button
  88. size="small"
  89. :plain="isUse !== '0'"
  90. :type="isUse === '0' ? 'primary' : 'default'"
  91. @click="setIsUse('0')"
  92. >
  93. {{ $t("discountCode.notUsed") }}
  94. </van-button>
  95. <van-button
  96. size="small"
  97. :plain="isUse !== '1'"
  98. :type="isUse === '1' ? 'primary' : 'default'"
  99. @click="setIsUse('1')"
  100. >
  101. {{ $t("discountCode.used") }}
  102. </van-button>
  103. </div>
  104. </div>
  105. <div class="discount-code-list">
  106. <van-list
  107. v-model:loading="loading"
  108. v-model:error="error"
  109. :error-text="$t('public.requestFailed')"
  110. :finished="finished"
  111. :finished-text="$t('public.noMore')"
  112. :offset="300"
  113. :immediate-check="false"
  114. @load="onLoad"
  115. >
  116. <!-- 优惠码列表 -->
  117. <div
  118. v-for="(item, index) in discountCodeList"
  119. :key="index"
  120. class="code-card"
  121. :class="{
  122. used: item.isUse === '1',
  123. expired: isExpired(item.lastUseDate),
  124. }"
  125. @click="disCountCodeClick(item)"
  126. >
  127. <div class="code-header">
  128. <div class="code-value">
  129. <span class="code-label"
  130. >{{ $t("discountCode.discountCode") }}:</span
  131. >
  132. {{ item.code }}
  133. </div>
  134. </div>
  135. <div class="code-details">
  136. <!-- 创建时间 -->
  137. <div v-if="item.isUse === '0'" class="detail-row">
  138. <van-icon name="clock" class="detail-icon" />
  139. <div class="detail-content">
  140. <div class="detail-label">
  141. {{ $t("discountCode.creationTime") }}:
  142. </div>
  143. <div class="detail-value">
  144. {{ showDateTime(item.createDate) }}
  145. </div>
  146. </div>
  147. </div>
  148. <!-- 所属商户 -->
  149. <div v-if="user.type < 2" class="detail-row">
  150. <van-icon name="shop" class="detail-icon" />
  151. <div class="detail-content">
  152. <div class="detail-label">
  153. {{ $t("discountCode.affiliatedMerchants") }}:
  154. </div>
  155. <div class="detail-value">{{ item.userName || "-" }}</div>
  156. </div>
  157. </div>
  158. <!-- 使用时间 -->
  159. <div v-if="item.isUse !== '0'" class="detail-row">
  160. <van-icon name="todo-list" class="detail-icon" />
  161. <div class="detail-content">
  162. <div class="detail-label">
  163. {{ $t("discountCode.usageTime") }}:
  164. </div>
  165. <div class="detail-value">
  166. {{ showDateTime(item.useDate) }}
  167. </div>
  168. </div>
  169. </div>
  170. <!-- 有效期 -->
  171. <div v-if="item.isUse === '0'" class="detail-row">
  172. <van-icon name="todo-list" class="detail-icon" />
  173. <div class="detail-content">
  174. <div class="detail-label">
  175. {{ $t("discountCode.termOfValidity") }}:
  176. </div>
  177. <div class="detail-value">
  178. {{ showDate(item.lastUseDate) }}
  179. <van-tag
  180. v-if="isExpired(item.lastUseDate)"
  181. type="danger"
  182. size="small"
  183. >
  184. {{ $t("discountCode.expired") }}
  185. </van-tag>
  186. </div>
  187. </div>
  188. </div>
  189. <!-- 使用设备 -->
  190. <div v-if="item.isUse !== '0'" class="detail-row">
  191. <van-icon name="setting" class="detail-icon" />
  192. <div class="detail-content">
  193. <div class="detail-label">
  194. {{ $t("discountCode.usingTheMachine") }}:
  195. </div>
  196. <div class="detail-value">{{ item.useBy || "-" }}</div>
  197. </div>
  198. </div>
  199. </div>
  200. <!-- 状态标签 -->
  201. <div v-if="!isDelete" class="status-tag">
  202. <van-tag
  203. :type="item.type === '1' ? 'danger' : 'warning'"
  204. size="large"
  205. >
  206. {{
  207. item.type == "1"
  208. ? $t("discountCode.deduction")
  209. : $t("discountCode.discount")
  210. }}:
  211. {{ item.discount }}
  212. </van-tag>
  213. <van-tag v-if="item.isUse === '0'" type="primary" size="large">
  214. {{ $t("discountCode.notUsed") }}
  215. </van-tag>
  216. <van-tag v-else-if="item.isUse === '1'" type="success" size="large">
  217. {{ $t("discountCode.used") }}
  218. </van-tag>
  219. </div>
  220. <!-- 删除模式下的选择框 -->
  221. <van-checkbox
  222. v-if="isDelete"
  223. v-model="item.checked"
  224. class="selection-checkbox"
  225. @click.stop
  226. />
  227. </div>
  228. </van-list>
  229. </div>
  230. <!-- 搜索弹出框 -->
  231. <codeSearch ref="searchRef" @search="search($event)"></codeSearch>
  232. <!-- 导出功能 -->
  233. <codeExport ref="exportRef" @exportCode="exportCode($event)"></codeExport>
  234. </div>
  235. </template>
  236. <script>
  237. import { onMounted, reactive, toRefs, ref, computed } from "vue";
  238. import sHeader from "@/components/SimpleHeader";
  239. import {
  240. getdiscountCodeList,
  241. discountCodeExport,
  242. deleteCode,
  243. } from "@/service/discountCode";
  244. import { showFailToast, showToast, showConfirmDialog } from "vant";
  245. import { getLoginUser, $M_ExportFile } from "@/common/js/utils";
  246. import codeSearch from "./codeSearch.vue";
  247. import codeExport from "./codeExport.vue";
  248. import dateUtil from "@/utils/dateUtil";
  249. import { useRouter } from "vue-router";
  250. import { useI18n } from "vue-i18n";
  251. export default {
  252. setup() {
  253. const { t } = useI18n();
  254. const router = useRouter();
  255. const searchRef = ref(null);
  256. const exportRef = ref(null);
  257. const user = getLoginUser();
  258. const loading = ref(false); // 加载状态
  259. const error = ref(false); // 错误状态
  260. const finished = ref(false); // 结束翻页状态
  261. const discountCodeList = ref([]); // 列表集合
  262. const discountCodeTotal = ref(0); // 列表总数
  263. let searchParams = reactive({
  264. adminId: "", // 用户账户id
  265. current: 1, // 页数
  266. size: 20, // 页大小
  267. isUse: "0", // 是否使用,0:未使用;1:已使用
  268. // type: null // 优惠码类型 0或null:折扣优惠码;1:抵扣价优惠码
  269. });
  270. // 初始化页面获取列表
  271. onMounted(async () => {
  272. //加载样式
  273. // 初始化列表及页码
  274. const user = getLoginUser();
  275. if (user) {
  276. searchParams.adminId = user.id;
  277. searchGetList();
  278. }
  279. });
  280. // 搜索弹窗触发搜索
  281. const search = (data) => {
  282. searchParams = Object.assign(searchParams, data);
  283. searchGetList();
  284. };
  285. // 导出弹窗触发导出
  286. const exportCode = (data) => {
  287. searchParams = Object.assign(searchParams, data);
  288. exportClick();
  289. };
  290. // 查询列表
  291. const searchGetList = () => {
  292. discountCodeList.value = [];
  293. searchParams.current = 1;
  294. getList();
  295. };
  296. // 滚动加载
  297. const onLoad = () => {
  298. if (!finished.value) {
  299. searchParams.current = searchParams.current + 1;
  300. getList();
  301. }
  302. };
  303. // 获取设备列表数据
  304. const getList = async () => {
  305. const { data } = await getdiscountCodeList(
  306. Object.assign({}, searchParams)
  307. );
  308. if (data.code === "00000") {
  309. // 加入删除选中状态
  310. if (data.data.records.length > 0) {
  311. data.data.records.forEach((item) => {
  312. item.checked = false;
  313. });
  314. }
  315. // 列表值叠加
  316. discountCodeList.value = discountCodeList.value.concat(
  317. data.data.records
  318. );
  319. // console.log(discountCodeList.value);
  320. discountCodeTotal.value = data.data.total;
  321. if (discountCodeList.value.length === data.data.total) {
  322. finished.value = true;
  323. }
  324. loading.value = false;
  325. } else {
  326. showFailToast(data.message);
  327. }
  328. };
  329. // 搜索点击
  330. const searchClick = () => {
  331. searchRef.value.showSearch();
  332. };
  333. // 导出点击
  334. const clickExport = () => {
  335. exportRef.value.showExport();
  336. };
  337. // 切换tab
  338. const setIsUse = (data) => {
  339. searchParams.isUse = data;
  340. searchGetList();
  341. };
  342. // 点击优惠码
  343. const disCountCodeClick = (data) => {
  344. console.log("disCountCodeClick", data);
  345. };
  346. // 时间格式化
  347. const showDate = (date) => {
  348. return date ? dateUtil.formateDate(new Date(date), "yyyy/MM/dd") : "";
  349. };
  350. const showDateTime = (date) => {
  351. return date
  352. ? dateUtil.formateDate(new Date(date), "yyyy/MM/dd hh:mm:ss")
  353. : "";
  354. };
  355. // 优惠码导出
  356. const exportClick = async () => {
  357. searchParams.size = 3000;
  358. const { headers, data } = await discountCodeExport(
  359. Object.assign({}, searchParams)
  360. );
  361. $M_ExportFile(data, headers);
  362. };
  363. const payCode = () => {
  364. router.push({ path: "/payCode" });
  365. };
  366. const isExpired = ref((lastUseDate) => {
  367. const currentDate = new Date();
  368. const lastUseDateTime = new Date(lastUseDate);
  369. return currentDate > lastUseDateTime;
  370. });
  371. // 是否删除状态
  372. const isDelete = ref(false); // 选中
  373. const selectTotals = computed(() => {
  374. let nums = 0;
  375. if (discountCodeList.value.length > 0) {
  376. discountCodeList.value.forEach((item) => {
  377. if (item.checked) {
  378. nums++;
  379. }
  380. });
  381. }
  382. return nums;
  383. });
  384. const cancelClk = () => {
  385. // 把选中状态去掉
  386. if (discountCodeList.value.length > 0) {
  387. discountCodeList.value.forEach((item) => {
  388. if (item.checked) {
  389. item.checked = false;
  390. }
  391. });
  392. }
  393. isDelete.value = false;
  394. };
  395. // 点击全选
  396. const allClk = () => {
  397. if (discountCodeList.value.length > 0) {
  398. discountCodeList.value.forEach((item) => {
  399. item.checked = true;
  400. });
  401. }
  402. };
  403. // 点击确认删除
  404. const noticeClk = () => {
  405. if (selectTotals.value < 1) {
  406. showToast(t("advertManage.delTips"));
  407. return;
  408. }
  409. showConfirmDialog({
  410. title: t("advertManage.delPopTitle"),
  411. message: t("advertManage.delPopContent"),
  412. })
  413. .then(() => {
  414. const ids = [];
  415. discountCodeList.value.forEach((item) => {
  416. if (item.checked) {
  417. ids.push(item.id);
  418. }
  419. });
  420. deleteCode(ids)
  421. .then((res) => {
  422. console.log(res);
  423. if (res.data.code === "00000") {
  424. isDelete.value = false;
  425. showToast(t("discountCode.deletionSucceeded"));
  426. setTimeout(() => {
  427. discountCodeList.value = [];
  428. getList();
  429. }, 1500);
  430. }
  431. })
  432. .catch(() => {
  433. isDelete.value = false;
  434. showToast("ERROR");
  435. });
  436. })
  437. .catch(() => {
  438. // showToast('ERROR');
  439. });
  440. };
  441. return {
  442. searchRef,
  443. exportRef,
  444. loading,
  445. error,
  446. finished,
  447. discountCodeList,
  448. discountCodeTotal,
  449. ...toRefs(searchParams),
  450. onLoad,
  451. searchClick,
  452. search,
  453. setIsUse,
  454. disCountCodeClick,
  455. showDate,
  456. showDateTime,
  457. // exportClick,
  458. payCode,
  459. isExpired,
  460. isDelete,
  461. allClk,
  462. noticeClk,
  463. selectTotals,
  464. cancelClk,
  465. user,
  466. clickExport,
  467. exportCode,
  468. };
  469. },
  470. components: { sHeader, codeSearch, codeExport },
  471. };
  472. </script>
  473. <style lang="less" scoped>
  474. @primary-color: #4d6add;
  475. @error-color: #ff4d4f;
  476. .discount-code-page {
  477. display: flex;
  478. flex-direction: column;
  479. height: 100vh;
  480. background-color: #f5f7fa;
  481. }
  482. .top-action-bar {
  483. display: flex;
  484. justify-content: space-between;
  485. align-items: center;
  486. padding: 15px;
  487. margin: 12px;
  488. background-color: white;
  489. border-radius: 12px;
  490. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  491. &.delete-mode {
  492. background-color: #fff8f6;
  493. border-left: 4px solid @error-color;
  494. }
  495. .status-display {
  496. font-size: 14px;
  497. color: #666;
  498. .count-highlight {
  499. font-weight: bold;
  500. font-size: 16px;
  501. color: @primary-color;
  502. margin: 0 4px;
  503. }
  504. }
  505. .action-buttons {
  506. display: flex;
  507. gap: 8px;
  508. }
  509. .action-button {
  510. min-width: auto;
  511. padding: 5px 10px;
  512. }
  513. }
  514. .tab-section {
  515. background-color: white;
  516. border-radius: 12px;
  517. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  518. padding: 15px;
  519. margin: 12px;
  520. .tab-buttons {
  521. display: flex;
  522. gap: 12px;
  523. .van-button {
  524. flex: 1;
  525. }
  526. }
  527. }
  528. .discount-code-list {
  529. flex: 1;
  530. overflow-y: auto;
  531. }
  532. .code-card {
  533. background-color: white;
  534. border-radius: 12px;
  535. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  536. padding: 16px;
  537. position: relative;
  538. overflow: hidden;
  539. transition: all 0.3s;
  540. margin: 12px;
  541. &:hover {
  542. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
  543. transform: translateY(-2px);
  544. }
  545. &.used {
  546. background-color: #f6ffed;
  547. }
  548. &.expired {
  549. border-left: 4px solid @error-color;
  550. opacity: 0.8;
  551. }
  552. .code-header {
  553. display: flex;
  554. // justify-content: space-between;
  555. gap: 10px;
  556. align-items: center;
  557. padding-bottom: 12px;
  558. margin-bottom: 12px;
  559. border-bottom: 1px dashed #f0f0f0;
  560. }
  561. .code-value {
  562. font-size: 16px;
  563. font-weight: bold;
  564. color: #333;
  565. .code-label {
  566. font-weight: normal;
  567. color: #666;
  568. margin-right: 8px;
  569. }
  570. }
  571. .code-type {
  572. .van-tag {
  573. border-radius: 4px;
  574. font-weight: 500;
  575. }
  576. }
  577. .code-details {
  578. .detail-row {
  579. display: flex;
  580. margin-bottom: 10px;
  581. align-items: flex-start;
  582. &:last-child {
  583. margin-bottom: 0;
  584. }
  585. }
  586. .detail-icon {
  587. font-size: 16px;
  588. color: #999;
  589. margin-right: 10px;
  590. margin-top: 3px;
  591. min-width: 16px;
  592. }
  593. .detail-content {
  594. flex: 1;
  595. }
  596. .detail-label {
  597. font-size: 13px;
  598. color: #999;
  599. margin-bottom: 2px;
  600. }
  601. .detail-value {
  602. font-size: 14px;
  603. color: #333;
  604. line-height: 1.4;
  605. }
  606. }
  607. .status-tag {
  608. display: flex;
  609. gap: 5px;
  610. position: absolute;
  611. top: 10px;
  612. right: 10px;
  613. z-index: 2;
  614. }
  615. .selection-checkbox {
  616. position: absolute;
  617. bottom: 10px;
  618. right: 10px;
  619. z-index: 2;
  620. }
  621. }
  622. @media (max-width: 480px) {
  623. .top-action-bar {
  624. padding: 12px;
  625. flex-direction: column;
  626. align-items: stretch;
  627. gap: 12px;
  628. .action-buttons {
  629. justify-content: space-between;
  630. }
  631. }
  632. .code-card {
  633. padding: 12px;
  634. .code-value {
  635. font-size: 15px;
  636. }
  637. .status-tag .van-tag {
  638. font-size: 12px;
  639. padding: 2px 6px;
  640. }
  641. }
  642. }
  643. </style>