index.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. <template>
  2. <div class="table-box button-table">
  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-btn">
  14. <div class="header-actions">
  15. <!-- 优惠券:状态 Tab(全部、未开始、进行中、已下架、已结束、已清库,不含草稿) -->
  16. <el-tabs v-if="activeName === '2'" v-model="couponStatusTab" class="status-tabs" @tab-click="onCouponStatusTabChange">
  17. <el-tab-pane v-for="tab in couponStatusTabOptions" :key="tab.value" :label="tab.label" :name="tab.value" />
  18. </el-tabs>
  19. </div>
  20. </div>
  21. </template>
  22. <template #tableHeaderRight="scope">
  23. <el-button :icon="Plus" class="button" type="primary" @click="newCoupon" v-if="type"> 新建优惠券 </el-button>
  24. <!-- <div class="action-buttons">-->
  25. <!-- <el-button :icon="Plus" class="button" type="primary" @click="newGroupBuying" v-if="type"> 新建代金券 </el-button>-->
  26. <!-- <el-button :icon="Plus" class="button" type="primary" @click="newCoupon" v-if="type"> 新建优惠券 </el-button>-->
  27. <!-- </div>-->
  28. </template>
  29. <template #status="scope">
  30. <!-- 代金券:显示状态和审核状态两行 -->
  31. <template v-if="activeName === '1'">
  32. <p style="margin: 0; line-height: 1.5">
  33. 状态:{{ scope.row.dataType === 1 ? "草稿" : scope.row.statusName ? scope.row.statusName : "--" }}
  34. </p>
  35. <p style="margin: 0; line-height: 1.5" v-if="scope.row.dataType == 0">审核状态:{{ scope.row.reviewType || "--" }}</p>
  36. </template>
  37. <!-- 优惠券:只显示状态一行 -->
  38. <template v-else>
  39. <span>{{ getStatusLabel(scope.row.status) }}</span>
  40. </template>
  41. </template>
  42. <!-- 表格操作 -->
  43. <template #operation="scope">
  44. <el-button v-if="canShowAction(scope.row, '上架')" link type="primary" @click="changeTypes(scope.row, 5)">
  45. 上架
  46. </el-button>
  47. <el-button v-if="canShowAction(scope.row, '下架')" link type="primary" @click="changeTypes(scope.row, 6)">
  48. 下架
  49. </el-button>
  50. <el-button v-if="canShowAction(scope.row, '修改库存')" link type="primary" @click="changeInventory(scope.row)">
  51. 修改库存
  52. </el-button>
  53. <el-button v-if="canShowAction(scope.row, '查看详情')" link type="primary" @click="toDetail(scope.row)">
  54. 查看详情
  55. </el-button>
  56. <el-button v-if="canShowAction(scope.row, '编辑')" link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
  57. <el-button v-if="canShowAction(scope.row, '删除')" link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
  58. <el-button v-if="canShowAction(scope.row, '查看拒绝原因')" link type="primary" @click="viewRejectReason(scope.row)">
  59. 查看拒绝原因
  60. </el-button>
  61. </template>
  62. </ProTable>
  63. <el-dialog v-model="dialogFormVisible" title="修改库存" width="500" append-to-body class="inventory-dialog-ios-fix">
  64. <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
  65. <el-form-item label="套餐名">
  66. {{ formInventory.name }}
  67. </el-form-item>
  68. <el-form-item label="剩余库存"> {{ formInventory.singleQty }}张 </el-form-item>
  69. <el-form-item label="修改库存" prop="newInventory">
  70. <el-input
  71. v-model="formInventory.newInventory"
  72. placeholder="请输入"
  73. inputmode="numeric"
  74. pattern="[0-9]*"
  75. autocomplete="off"
  76. @focus="onInventoryInputFocus"
  77. />
  78. </el-form-item>
  79. </el-form>
  80. <template #footer>
  81. <div class="dialog-footer">
  82. <el-button @click="closeDialog"> 取消 </el-button>
  83. <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
  84. </div>
  85. </template>
  86. </el-dialog>
  87. <!-- 查看拒绝原因弹窗 -->
  88. <el-dialog v-model="rejectReasonDialogVisible" title="查看拒绝原因" width="600px">
  89. <div class="reject-reason-content">
  90. <div class="reject-reason-item">
  91. <div class="reject-reason-label">代金券名称:</div>
  92. <div class="reject-reason-value">
  93. {{ rejectReasonData.name || "--" }}
  94. </div>
  95. </div>
  96. <div class="reject-reason-item">
  97. <div class="reject-reason-label">拒绝原因:</div>
  98. <div class="reject-reason-value reject-reason-text">
  99. {{ rejectReasonData.approvalComments || "暂无拒绝原因" }}
  100. </div>
  101. </div>
  102. </div>
  103. <template #footer>
  104. <div class="dialog-footer">
  105. <el-button type="primary" @click="closeRejectReasonDialog"> 确定 </el-button>
  106. </div>
  107. </template>
  108. </el-dialog>
  109. </div>
  110. </template>
  111. <script setup lang="tsx" name="voucherManagement">
  112. import { computed, nextTick, onActivated, onMounted, reactive, ref, watch } from "vue";
  113. import { useRouter, useRoute } from "vue-router";
  114. import type { FormInstance, FormRules } from "element-plus";
  115. import { ElMessage } from "element-plus";
  116. import ProTable from "@/components/ProTable/index.vue";
  117. import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
  118. import { Plus } from "@element-plus/icons-vue";
  119. import { delThaliById, getThaliList, updateNum, updateStatus } from "@/api/modules/voucherManagement";
  120. import { delCouponById, getStoreAllCouponList, updateCouponSingleQty, updateCouponStatus } from "@/api/modules/couponManagement";
  121. import { ElMessageBox } from "element-plus/es";
  122. import { localGet, usePermission } from "@/utils";
  123. import { formatCurrency } from "@/utils/formatCurrency";
  124. const router = useRouter();
  125. const route = useRoute();
  126. const dialogFormVisible = ref(false);
  127. const formInventory: any = ref({
  128. id: "",
  129. name: "",
  130. singleQty: "",
  131. newInventory: ""
  132. });
  133. const activeName = ref("2");
  134. // 查看拒绝原因弹窗相关
  135. const rejectReasonDialogVisible = ref(false);
  136. const rejectReasonData = ref<any>({
  137. name: "",
  138. approvalComments: ""
  139. });
  140. // 定义表单类型
  141. interface RuleForm {
  142. newInventory: string;
  143. }
  144. const ruleFormRef = ref<FormInstance>();
  145. const rules = reactive<FormRules<RuleForm>>({
  146. newInventory: [
  147. { required: true, message: "请输入库存数量", trigger: "blur" },
  148. {
  149. pattern: /^(0|[1-9][0-9]*)$/,
  150. message: "请输入整数,不允许输入小数,负数",
  151. trigger: "blur"
  152. },
  153. {
  154. validator: (rule: any, value: any, callback: any) => {
  155. if (value) {
  156. const numValue = Number(value);
  157. if (!isNaN(numValue) && numValue > 10000) {
  158. callback(new Error("库存不得大于10000"));
  159. return;
  160. }
  161. // 验证新库存值不能小于剩余库存
  162. const currentQty = Number(formInventory.value.singleQty) || 0;
  163. if (numValue < currentQty) {
  164. callback(new Error(`新库存值不能小于剩余库存(${currentQty})张`));
  165. return;
  166. }
  167. }
  168. callback();
  169. },
  170. trigger: "blur"
  171. }
  172. ]
  173. });
  174. const statusEnum = [
  175. { label: "草稿", value: "0" },
  176. { label: "进行中", value: "5" },
  177. { label: "未开始", value: "2" },
  178. { label: "已下架", value: "6" },
  179. { label: "已售罄", value: "4" },
  180. { label: "已结束", value: "7" }
  181. ];
  182. const statusEnumY = [
  183. { label: "未开始", value: "2" },
  184. { label: "进行中", value: "0" },
  185. { label: "已下架", value: "3" },
  186. { label: "已结束", value: "1" },
  187. { label: "已清库", value: "4" }
  188. ];
  189. // 优惠券状态 Tab 选项(不含草稿)
  190. const couponStatusTabOptions = [
  191. { label: "全部", value: "0" },
  192. { label: "进行中", value: "1" },
  193. // { label: "草稿", value: "3" },
  194. { label: "已结束", value: "2" },
  195. { label: "已下架", value: "5" },
  196. { label: "未开始", value: "4" },
  197. { label: "已清库", value: "6" }
  198. ];
  199. const couponStatusTab = ref("0");
  200. // 优惠券类型枚举(满减券、折扣券)
  201. const couponTypeEnum = [
  202. { label: "满减券", value: "1" },
  203. { label: "折扣券", value: "2" }
  204. ];
  205. // ProTable 实例(需要在使用它的地方之前定义)
  206. const proTable = ref<ProTableInstance>();
  207. // 代金券表格列配置
  208. const voucherColumns = reactive<ColumnProps<any>[]>([
  209. {
  210. prop: "name",
  211. label: "券名称",
  212. search: {
  213. el: "input"
  214. }
  215. },
  216. {
  217. prop: "price",
  218. label: "价格",
  219. render: (scope: any) => {
  220. return formatCurrency(scope.row.price, 2, "¥") || "--";
  221. }
  222. },
  223. {
  224. prop: "saleNum",
  225. label: "已售",
  226. render: scope => {
  227. return scope.row.saleNum === null || scope.row.saleNum === undefined || scope.row.saleNum === "" ? 0 : scope.row.saleNum;
  228. }
  229. },
  230. {
  231. prop: "singleQty",
  232. label: "剩余库存",
  233. render: scope => {
  234. return scope.row.singleQty === null || scope.row.singleQty === undefined || scope.row.singleQty === ""
  235. ? 0
  236. : scope.row.singleQty + "张";
  237. }
  238. },
  239. {
  240. prop: "validDate",
  241. label: "结束时间",
  242. render: (scope: any) => {
  243. return scope.row.validDate?.replace(/-/g, "/") || "--";
  244. }
  245. },
  246. {
  247. prop: "status",
  248. label: "状态",
  249. search: {
  250. el: "select",
  251. props: { placeholder: "请选择" }
  252. },
  253. enum: statusEnum,
  254. fieldNames: { label: "label", value: "value" }
  255. },
  256. { prop: "operation", label: "操作", fixed: "right", width: 330 }
  257. ]);
  258. // 优惠券表格列配置(参考商家端:优惠券名称、剩余库存、结束时间、状态、类型、操作)
  259. const couponColumns = reactive<ColumnProps<any>[]>([
  260. {
  261. prop: "name",
  262. label: "优惠券名称",
  263. search: {
  264. el: "input",
  265. props: { placeholder: "请输入" }
  266. }
  267. },
  268. {
  269. prop: "singleQty",
  270. label: "剩余库存",
  271. render: scope => {
  272. return scope.row.singleQty === null || scope.row.singleQty === undefined || scope.row.singleQty === ""
  273. ? 0
  274. : scope.row.singleQty;
  275. }
  276. },
  277. {
  278. prop: "validDate",
  279. label: "结束时间",
  280. render: (scope: any) => {
  281. return scope.row.validDate?.replace(/-/g, "/") || "--";
  282. }
  283. },
  284. {
  285. prop: "status",
  286. label: "状态"
  287. },
  288. {
  289. prop: "couponType",
  290. label: "类型",
  291. search: {
  292. el: "select",
  293. props: { placeholder: "请选择" }
  294. },
  295. enum: couponTypeEnum,
  296. fieldNames: { label: "label", value: "value" },
  297. render: (scope: any) => getCouponTypeLabel(scope.row.couponType)
  298. },
  299. { prop: "operation", label: "操作", fixed: "right", width: 280 }
  300. ]);
  301. // 根据当前选中的tab动态返回列配置
  302. const columns = computed(() => {
  303. return activeName.value === "1" ? voucherColumns : couponColumns;
  304. });
  305. const allTabOptions = [
  306. { label: "代金券", name: "1" },
  307. { label: "优惠券", name: "2" }
  308. ];
  309. // 状态枚举: 1待审核 2未开始 3审核拒绝 4已售罄 5进行中 6已下架 7已结束
  310. const VO_STATUS = {
  311. 待审核: 1,
  312. 未开始: 2,
  313. 审核拒绝: 3,
  314. 已售罄: 4,
  315. 进行中: 5,
  316. 已下架: 6,
  317. 已结束: 7
  318. } as const;
  319. // 状态枚举:0进行中 1已结束 2未开始 3已下架 4已售罄 5草稿
  320. const CO_STATUS = {
  321. 进行中: 0,
  322. 已结束: 1,
  323. 未开始: 2,
  324. 已下架: 3,
  325. 已售罄: 4,
  326. 草稿: 5
  327. } as const;
  328. // 按「状态 → 可执行操作」配置,与产品表一致,一目了然
  329. // 代金券:各状态下的操作列表(草稿用 dataType==1 单独判断)
  330. const VOUCHER_ACTIONS_BY_STATUS: Record<number, string[]> = {
  331. [VO_STATUS.待审核]: ["查看详情", "查看拒绝原因"],
  332. [VO_STATUS.未开始]: ["查看详情", "上架", "修改库存", "删除"],
  333. [VO_STATUS.审核拒绝]: ["查看详情", "编辑", "删除", "查看拒绝原因"],
  334. [VO_STATUS.进行中]: ["查看详情", "下架", "修改库存"],
  335. [VO_STATUS.已售罄]: ["查看详情", "编辑", "修改库存", "删除"],
  336. [VO_STATUS.已下架]: ["查看详情", "上架", "编辑", "删除"],
  337. [VO_STATUS.已结束]: ["编辑", "删除"]
  338. };
  339. // 代金券草稿(dataType==1)仅显示:编辑、删除
  340. const VOUCHER_DRAFT_ACTIONS = ["编辑", "删除"];
  341. // 优惠券:各状态下的操作列表(完全按产品表)
  342. const COUPON_ACTIONS_BY_STATUS: Record<number, string[]> = {
  343. [CO_STATUS.草稿]: ["查看详情", "编辑", "删除"],
  344. [CO_STATUS.未开始]: ["查看详情", "修改库存"],
  345. [CO_STATUS.进行中]: ["下架", "查看详情", "修改库存"],
  346. [CO_STATUS.已下架]: ["上架", "查看详情", "修改库存"],
  347. [CO_STATUS.已结束]: ["查看详情", "删除"],
  348. [CO_STATUS.已售罄]: ["查看详情", "编辑", "删除"]
  349. };
  350. /** 判断当前行是否显示某操作按钮(代金券/优惠券通用) */
  351. const canShowAction = (row: any, actionName: string): boolean => {
  352. if (activeName.value === "1") {
  353. if (row.dataType === 1) return VOUCHER_DRAFT_ACTIONS.includes(actionName);
  354. const actions = VOUCHER_ACTIONS_BY_STATUS[row.status];
  355. return actions != null && actions.includes(actionName);
  356. }
  357. const actions = COUPON_ACTIONS_BY_STATUS[row.status];
  358. return actions != null && actions.includes(actionName);
  359. };
  360. // 判断是否为代金券
  361. const isVoucher = computed(() => activeName.value === "1");
  362. // 判断是否为优惠券
  363. const isCoupon = computed(() => activeName.value === "2");
  364. // 获取状态标签
  365. const getStatusLabel = (status: any) => {
  366. const statusEnumMap = CO_STATUS;
  367. for (const [label, value] of Object.entries(statusEnumMap)) {
  368. if (value === status) {
  369. return label;
  370. }
  371. }
  372. return "--";
  373. };
  374. // 优惠券类型展示(满减券/折扣券)
  375. const getCouponTypeLabel = (type: number | string) => {
  376. const t = type === undefined || type === null ? "" : String(type);
  377. const item = couponTypeEnum.find(e => e.value === t);
  378. return item ? item.label : "--";
  379. };
  380. // 如果表格需要初始化请求参数,直接定义传给 ProTable
  381. // 注意:不用 couponType,避免与搜索项「类型」(满减券/折扣券) 冲突,用 listType 表示 代金券(1)/优惠券(2)
  382. const initParam = reactive({
  383. storeId: localGet("createdId") || "",
  384. groupType: localGet("businessSection") || "1",
  385. listType: activeName
  386. });
  387. const type = ref(false);
  388. const typeCoupon = ref(false);
  389. // 恢复 activeName 的函数
  390. const restoreActiveName = () => {
  391. const savedActiveName = sessionStorage.getItem("ticketManagement_activeName");
  392. if (savedActiveName && (savedActiveName === "1" || savedActiveName === "2")) {
  393. activeName.value = savedActiveName;
  394. // 恢复后清除 sessionStorage,避免影响其他场景
  395. sessionStorage.removeItem("ticketManagement_activeName");
  396. }
  397. };
  398. // 页面加载时触发查询
  399. onMounted(async () => {
  400. type.value = await usePermission("新建优惠券");
  401. // typeCoupon.value = await usePermission("新建优惠券");
  402. // 从 sessionStorage 恢复 activeName
  403. restoreActiveName();
  404. proTable.value?.getTableList();
  405. });
  406. // 从其他页面返回时触发查询
  407. onActivated(() => {
  408. // 从 sessionStorage 恢复 activeName
  409. restoreActiveName();
  410. proTable.value?.getTableList();
  411. });
  412. // 监听路由变化,当从详情页或编辑页返回时恢复 activeName
  413. watch(
  414. () => route.path,
  415. (newPath, oldPath) => {
  416. // 如果当前路径是列表页,且之前路径是详情页或编辑页,则恢复 activeName
  417. if (
  418. newPath.includes("/ticketManagement") &&
  419. !newPath.includes("/detail") &&
  420. !newPath.includes("/newVoucher") &&
  421. !newPath.includes("/newCoupon") &&
  422. (oldPath?.includes("/ticketManagement/detail") ||
  423. oldPath?.includes("/ticketManagement/couponDetail") ||
  424. oldPath?.includes("/ticketManagement/newVoucher") ||
  425. oldPath?.includes("/ticketManagement/newCoupon"))
  426. ) {
  427. restoreActiveName();
  428. }
  429. },
  430. { immediate: false }
  431. );
  432. // dataCallback 是对于返回的表格数据做处理
  433. const dataCallback = (data: any) => {
  434. if (activeName.value === "1") {
  435. return {
  436. list: data.couponList?.records || [],
  437. total: data.couponList?.total || 0
  438. };
  439. } else {
  440. // 优惠券:新接口 getStoreAllCouponList 返回格式兼容 data.data 或 data.records
  441. const raw = data?.data ?? data;
  442. return {
  443. list: raw?.records ?? raw?.list ?? [],
  444. total: raw?.total ?? 0
  445. };
  446. }
  447. };
  448. // 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
  449. const getTableList = (params: any) => {
  450. let newParams = JSON.parse(JSON.stringify(params));
  451. if (activeName.value === "1") {
  452. // 代金券:使用 voucherManagement 接口,listType 即 代金券/优惠券 区分
  453. newParams.couponType = newParams.listType;
  454. if (newParams.status === "0") {
  455. newParams.dataType = 1; // 草稿
  456. } else if (newParams.status) {
  457. newParams.dataType = 0;
  458. } else {
  459. newParams.dataType = 2;
  460. }
  461. return getThaliList(newParams);
  462. } else {
  463. // 优惠券:使用 getStoreAllCouponList,搜索项 name、couponType 需一并传入
  464. return getStoreAllCouponList({
  465. storeId: newParams.storeId || localGet("createdId") || "",
  466. tab: couponStatusTab.value || "",
  467. size: newParams.pageSize || 10,
  468. page: newParams.pageNum || 1,
  469. couponName: newParams.name ?? "",
  470. couponType: newParams.couponType ?? "",
  471. couponsFromType: 1
  472. });
  473. }
  474. };
  475. const newGroupBuying = () => {
  476. router.push(`/ticketManagement/newVoucher?type=add`);
  477. };
  478. const newCoupon = () => {
  479. router.push(`/ticketManagement/newCoupon?type=add`);
  480. };
  481. // 跳转详情页 - 根据类型跳转不同页面
  482. const toDetail = (row: any) => {
  483. // 保存当前 activeName 到 sessionStorage
  484. sessionStorage.setItem("ticketManagement_activeName", activeName.value);
  485. const path = isVoucher.value ? `/ticketManagement/detail?id=${row.id}` : `/ticketManagement/couponDetail?id=${row.id}`;
  486. router.push(path);
  487. };
  488. // 编辑行数据
  489. const editRow = (row: any) => {
  490. // 保存当前 activeName 到 sessionStorage
  491. sessionStorage.setItem("ticketManagement_activeName", activeName.value);
  492. const path = isVoucher.value
  493. ? `/ticketManagement/newVoucher?id=${row.id}&type=edit`
  494. : `/ticketManagement/newCoupon?id=${row.id}&type=edit`;
  495. router.push(path);
  496. };
  497. // 删除行数据
  498. const deleteRow = (row: any) => {
  499. ElMessageBox.confirm("确定要删除吗?", "提示", {
  500. confirmButtonText: "确定",
  501. cancelButtonText: "取消",
  502. type: "warning"
  503. })
  504. .then(() => {
  505. if (isVoucher.value) {
  506. // 代金券删除逻辑
  507. const params = {
  508. id: row.id,
  509. groupType: localGet("businessSection")
  510. };
  511. return delThaliById(params);
  512. } else {
  513. // 优惠券删除逻辑
  514. const params = {
  515. id: row.id,
  516. groupType: localGet("businessSection")
  517. };
  518. return delCouponById(params);
  519. }
  520. })
  521. .then(() => {
  522. ElMessage.success("删除成功");
  523. proTable.value?.getTableList();
  524. })
  525. .catch(() => {
  526. // 用户取消删除,不做任何操作
  527. });
  528. };
  529. // Tab切换处理
  530. const handleClick = () => {};
  531. // 优惠券状态 Tab 切换
  532. // 优惠券状态 Tab 切换(tab-click 触发时 v-model 尚未更新,用 nextTick 等绑定更新后再请求)
  533. const onCouponStatusTabChange = () => {
  534. nextTick(() => {
  535. proTable.value?.getTableList();
  536. });
  537. };
  538. // 修改状态(上架/下架)
  539. const changeTypes = async (row: any, status: number) => {
  540. if (isVoucher.value) {
  541. // 代金券上架/下架逻辑
  542. const res = await updateStatus({ id: row.id, status: status, approvalComments: "" });
  543. if (res && Number((res as any).code) === 200) {
  544. ElMessage.success("操作成功");
  545. proTable.value?.getTableList();
  546. }
  547. } else {
  548. // 优惠券上架/下架逻辑
  549. const res = await updateCouponStatus({ counponId: row.id });
  550. if (res && Number((res as any).code) === 200) {
  551. ElMessage.success("操作成功");
  552. proTable.value?.getTableList();
  553. }
  554. }
  555. };
  556. // 修改库存
  557. const changeInventory = (row: any) => {
  558. formInventory.value = {
  559. id: row.id,
  560. name: row.name,
  561. singleQty: row.singleQty,
  562. newInventory: ""
  563. };
  564. dialogFormVisible.value = true;
  565. };
  566. // 提交修改库存
  567. const handleSubmit = async () => {
  568. if (!ruleFormRef.value) return;
  569. await ruleFormRef.value.validate(async valid => {
  570. if (valid) {
  571. const params = {
  572. id: formInventory.value.id,
  573. singleQty: formInventory.value.newInventory
  574. };
  575. if (isVoucher.value) {
  576. const res = await updateNum(params);
  577. if (res && Number((res as any).code) === 200) {
  578. ElMessage.success("修改成功");
  579. closeDialog();
  580. proTable.value?.getTableList();
  581. }
  582. } else {
  583. const res = await updateCouponSingleQty(params);
  584. if (res && Number((res as any).code) === 200) {
  585. ElMessage.success("修改成功");
  586. closeDialog();
  587. proTable.value?.getTableList();
  588. }
  589. }
  590. }
  591. });
  592. };
  593. // 关闭弹窗
  594. const closeDialog = () => {
  595. dialogFormVisible.value = false;
  596. formInventory.value = {
  597. id: "",
  598. name: "",
  599. singleQty: "",
  600. newInventory: ""
  601. };
  602. };
  603. // iOS 上输入框获焦时滚动到可视区域,避免键盘遮挡底部按钮
  604. const onInventoryInputFocus = (e: Event) => {
  605. const target = e.target as HTMLElement;
  606. if (!target) return;
  607. requestAnimationFrame(() => {
  608. target.scrollIntoView({ behavior: "smooth", block: "center" });
  609. });
  610. };
  611. // 查看拒绝原因
  612. const viewRejectReason = (row: any) => {
  613. rejectReasonData.value = {
  614. name: row.name,
  615. approvalComments: row.approvalComments
  616. };
  617. rejectReasonDialogVisible.value = true;
  618. };
  619. // 关闭拒绝原因弹窗
  620. const closeRejectReasonDialog = () => {
  621. rejectReasonDialogVisible.value = false;
  622. };
  623. </script>
  624. <style lang="scss" scoped>
  625. /* iOS 上修改库存弹窗:避免键盘弹起时遮挡底部,弹窗可滚动 */
  626. :deep(.inventory-dialog-ios-fix) {
  627. .el-dialog__body {
  628. max-height: 60vh;
  629. overflow-y: auto;
  630. -webkit-overflow-scrolling: touch;
  631. }
  632. }
  633. // 在组件样式中添加
  634. .date-range {
  635. display: block; // 确保换行生效
  636. padding: 0 8px; // 可选:增加内边距
  637. word-wrap: break-word; // 长单词内换行
  638. white-space: normal; // 允许自然换行
  639. }
  640. .table-header-btn {
  641. .header-actions {
  642. display: flex;
  643. flex-wrap: wrap;
  644. gap: 12px;
  645. align-items: center;
  646. justify-content: space-between;
  647. width: 100%;
  648. .status-tabs {
  649. flex: 0 0 auto;
  650. :deep(.el-tabs__header) {
  651. margin-bottom: 0;
  652. }
  653. :deep(.el-tabs__nav-wrap::after) {
  654. height: 0;
  655. }
  656. }
  657. .button {
  658. margin-bottom: 0;
  659. }
  660. }
  661. }
  662. .action-buttons {
  663. display: flex;
  664. flex: 0 0 auto;
  665. gap: 10px;
  666. margin-right: 20px;
  667. .button {
  668. margin-bottom: 0;
  669. }
  670. }
  671. .reject-reason-content {
  672. padding: 20px 0;
  673. .reject-reason-item {
  674. display: flex;
  675. margin-bottom: 20px;
  676. &:last-child {
  677. margin-bottom: 0;
  678. }
  679. .reject-reason-label {
  680. flex-shrink: 0;
  681. min-width: 100px;
  682. font-size: 14px;
  683. font-weight: 500;
  684. color: #606266;
  685. }
  686. .reject-reason-value {
  687. flex: 1;
  688. font-size: 14px;
  689. color: #303133;
  690. word-break: break-word;
  691. &.reject-reason-text {
  692. min-height: 80px;
  693. padding: 12px;
  694. line-height: 1.6;
  695. white-space: pre-wrap;
  696. background-color: #f5f7fa;
  697. border-radius: 4px;
  698. }
  699. }
  700. }
  701. }
  702. </style>