index.vue 22 KB

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