newVoucher.vue 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473
  1. <template>
  2. <!-- 代金券管理 - 新增/编辑页面 -->
  3. <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
  4. <div class="header">
  5. <el-button @click="goBack"> 返回 </el-button>
  6. <h2 class="title">{{ type == "add" ? "新建" : "编辑" }}代金券</h2>
  7. </div>
  8. <el-form :model="voucherModel" ref="ruleFormRef" :rules="rules" label-width="130px" class="formBox">
  9. <div class="content">
  10. <!-- 左侧内容区域 -->
  11. <div class="contentLeft">
  12. <!-- 基础信息模块 -->
  13. <div class="model">
  14. <h3 style="font-weight: bold">基础信息:</h3>
  15. <!-- 代金券名称 -->
  16. <el-form-item label="代金券名称" prop="name">
  17. <el-input maxlength="20" v-model="voucherModel.name" placeholder="请输入" clearable />
  18. </el-form-item>
  19. <!-- 抵扣价格 -->
  20. <el-form-item label="抵扣价格(¥)" prop="price">
  21. <el-input v-model="voucherModel.price" maxlength="15" placeholder="请输入" clearable />
  22. </el-form-item>
  23. <!-- 售卖价格 -->
  24. <el-form-item label="售卖价格(¥)" prop="offprice">
  25. <el-input v-model="voucherModel.offprice" maxlength="15" placeholder="请输入" clearable />
  26. </el-form-item>
  27. <!-- 开始售卖时间 -->
  28. <el-form-item label="开始售卖时间" prop="startDate">
  29. <el-date-picker
  30. v-model="voucherModel.startDate"
  31. format="YYYY/MM/DD"
  32. value-format="YYYY-MM-DD"
  33. placeholder="请选择开始售卖时间"
  34. :disabled-date="disabledStartDate"
  35. />
  36. </el-form-item>
  37. <!-- 结束售卖时间 -->
  38. <el-form-item label="结束售卖时间" prop="endDate">
  39. <el-date-picker
  40. v-model="voucherModel.endDate"
  41. format="YYYY/MM/DD"
  42. value-format="YYYY-MM-DD"
  43. placeholder="请选择结束售卖时间"
  44. :disabled-date="disabledEndDate"
  45. />
  46. </el-form-item>
  47. </div>
  48. <!-- 购买须知模块 -->
  49. <div class="model">
  50. <h3 style="font-weight: bold">购买须知:</h3>
  51. <!-- 使用时间 -->
  52. <el-form-item label="使用时间" prop="usageTime">
  53. <div class="time-range-container">
  54. <el-select v-model="voucherModel.buyUseStartTime" placeholder="开始时间" class="time-picker">
  55. <el-option v-for="hour in hourOptions" :key="hour.value" :label="hour.label" :value="hour.value" />
  56. </el-select>
  57. <span class="time-separator">至</span>
  58. <el-select v-model="voucherModel.buyUseEndTime" placeholder="结束时间" class="time-picker">
  59. <el-option v-for="hour in hourOptions" :key="hour.value" :label="hour.label" :value="hour.value" />
  60. </el-select>
  61. </div>
  62. </el-form-item>
  63. <!-- 有效期 -->
  64. <el-form-item label="有效期" prop="expirationType">
  65. <el-radio-group v-model="voucherModel.expirationType" class="ml-4">
  66. <el-radio v-for="status in validityPeriodList" :value="status.value" :key="status.value">
  67. {{ status.label }}
  68. </el-radio>
  69. </el-radio-group>
  70. </el-form-item>
  71. <el-form-item label="" prop="expirationDate" v-if="voucherModel.expirationType == 1">
  72. <div class="expiration-date-container">
  73. <span class="expiration-label">用户购买</span>
  74. <el-input-number
  75. v-model="voucherModel.expirationDate"
  76. placeholder="请输入"
  77. :min="0"
  78. :max="10000"
  79. class="expiration-input"
  80. />
  81. <span class="expiration-label">天内有效</span>
  82. </div>
  83. </el-form-item>
  84. <el-form-item label="" prop="validityPeriod" v-else>
  85. <el-date-picker
  86. v-model="voucherModel.validityPeriod"
  87. type="daterange"
  88. value-format="x"
  89. range-separator="-"
  90. start-placeholder="开始时间"
  91. end-placeholder="结束时间"
  92. :disabled-date="disabledValidityDate"
  93. />
  94. </el-form-item>
  95. <!-- 不可用日期 -->
  96. <el-form-item label="不可用日期" prop="unusedType">
  97. <el-radio-group v-model="voucherModel.unusedType" class="ml-4">
  98. <el-radio v-for="status in unavailableDateTypeList" :value="status.value" :key="status.value">
  99. {{ status.label }}
  100. </el-radio>
  101. </el-radio-group>
  102. </el-form-item>
  103. <template v-if="voucherModel.unusedType == 2">
  104. <el-form-item label="" prop="unavailableWeekdays">
  105. <div class="unavailable-dates-container">
  106. <!-- 星期选择 -->
  107. <div class="date-select-section">
  108. <div class="section-title">星期</div>
  109. <div class="button-group">
  110. <el-button
  111. v-for="weekday in weekdayList"
  112. :key="weekday.oName"
  113. :type="voucherModel.unavailableWeekdays?.includes(weekday.oName) ? 'primary' : ''"
  114. class="date-select-btn"
  115. @click="toggleWeekday(weekday.oName)"
  116. >
  117. {{ weekday.name }}
  118. </el-button>
  119. </div>
  120. </div>
  121. </div>
  122. </el-form-item>
  123. <el-form-item label="" prop="unavailableHolidays">
  124. <div class="unavailable-dates-container">
  125. <!-- 节日选择 -->
  126. <div class="date-select-section">
  127. <div class="section-title">节日</div>
  128. <div class="button-group">
  129. <el-button
  130. v-for="holiday in holidayList"
  131. :key="holiday.id"
  132. :type="voucherModel.unavailableHolidays?.includes(String(holiday.id)) ? 'primary' : ''"
  133. class="date-select-btn"
  134. @click="toggleHoliday(holiday.id)"
  135. >
  136. {{ holiday.festivalName }}
  137. </el-button>
  138. </div>
  139. </div>
  140. </div>
  141. </el-form-item>
  142. </template>
  143. <el-form-item label="" prop="customUnavailableDates" v-else-if="voucherModel.unusedType == 3">
  144. <div class="date-picker-container">
  145. <el-button :icon="Plus" class="add-date-btn" type="primary" @click="addDate"> 添加日期 </el-button>
  146. <div v-for="(item, index) in voucherModel.disableDateList" :key="index" class="date-item">
  147. <el-date-picker
  148. v-model="voucherModel.disableDateList[index]"
  149. type="daterange"
  150. value-format="YYYY-MM-DD"
  151. range-separator="-"
  152. start-placeholder="开始时间"
  153. end-placeholder="结束时间"
  154. class="date-picker"
  155. :disabled-date="disabledCustomUnavailableDate"
  156. />
  157. <el-button
  158. :icon="Delete"
  159. type="danger"
  160. circle
  161. size="small"
  162. class="delete-btn"
  163. @click="removeDate(index)"
  164. v-show="voucherModel.disableDateList.length > 1"
  165. />
  166. </div>
  167. </div>
  168. </el-form-item>
  169. </div>
  170. </div>
  171. <!-- 右侧内容区域 -->
  172. <div class="contentRight">
  173. <!-- 库存模块 -->
  174. <div class="model">
  175. <h3 style="font-weight: bold">库存:</h3>
  176. <el-form-item label="库存(张)" prop="singleQty">
  177. <el-input
  178. v-model="voucherModel.singleQty"
  179. maxlength="15"
  180. placeholder="请输入"
  181. clearable
  182. :validate-event="!isInitializing"
  183. />
  184. </el-form-item>
  185. </div>
  186. <!-- 使用规则模块 -->
  187. <div class="model">
  188. <h3 style="font-weight: bold">使用规则:</h3>
  189. <!-- 单日可用数量 -->
  190. <el-form-item label="单日可用数量(张)" prop="singleCanUseType">
  191. <el-radio-group v-model="voucherModel.singleCanUseType" class="ml-4">
  192. <el-radio v-for="status in dailyUseLimitList" :value="status.value" :key="status.value">
  193. {{ status.label }}
  194. </el-radio>
  195. </el-radio-group>
  196. </el-form-item>
  197. <el-form-item label="" prop="singleCanUse" v-if="voucherModel.singleCanUseType == 2">
  198. <el-input v-model="voucherModel.singleCanUse" maxlength="15" placeholder="请输入" clearable />
  199. </el-form-item>
  200. <!-- 限购数量 -->
  201. <el-form-item label="限购数量(张)" prop="purchaseLimitType">
  202. <el-radio-group v-model="voucherModel.purchaseLimitType" class="ml-4">
  203. <el-radio v-for="status in purchaseLimitList" :value="status.value" :key="status.value">
  204. {{ status.label }}
  205. </el-radio>
  206. </el-radio-group>
  207. </el-form-item>
  208. <el-form-item label="" prop="purchaseLimitCode" v-if="voucherModel.purchaseLimitType == 2">
  209. <el-input v-model="voucherModel.purchaseLimitCode" maxlength="15" placeholder="请输入" clearable />
  210. </el-form-item>
  211. <!-- 适用范围 -->
  212. <el-form-item label="适用范围" prop="applyType">
  213. <el-radio-group v-model="voucherModel.applyType" class="ml-4">
  214. <el-radio v-for="status in applicableScopeList" :value="status.value" :key="status.value">
  215. {{ status.label }}
  216. </el-radio>
  217. </el-radio-group>
  218. </el-form-item>
  219. <el-form-item label="" prop="applyDesc" v-if="voucherModel.applyType == 2">
  220. <el-input
  221. maxlength="50"
  222. v-model="voucherModel.applyDesc"
  223. :rows="3"
  224. type="textarea"
  225. placeholder="请输入"
  226. show-word-limit
  227. />
  228. </el-form-item>
  229. </div>
  230. <!-- 补充说明模块 -->
  231. <div class="model">
  232. <h3 style="font-weight: bold">补充说明:</h3>
  233. <el-form-item label="补充说明" prop="supplement">
  234. <el-input
  235. maxlength="300"
  236. v-model="voucherModel.supplement"
  237. :rows="4"
  238. type="textarea"
  239. placeholder="请输入"
  240. show-word-limit
  241. />
  242. </el-form-item>
  243. </div>
  244. </div>
  245. </div>
  246. </el-form>
  247. <!-- 底部按钮区域 -->
  248. <div class="button-container">
  249. <el-button @click="handleSubmit('draft')"> 存草稿 </el-button>
  250. <el-button type="primary" @click="handleSubmit()"> 提交 </el-button>
  251. </div>
  252. </div>
  253. </template>
  254. <script setup lang="tsx" name="newVoucher">
  255. /**
  256. * 代金券管理 - 新增/编辑页面
  257. * 功能:支持代金券的新增和编辑操作
  258. */
  259. import { ref, reactive, watch, nextTick, onMounted } from "vue";
  260. import { ElMessage } from "element-plus";
  261. import { Plus, Delete } from "@element-plus/icons-vue";
  262. import { useRoute, useRouter } from "vue-router";
  263. import type { FormInstance } from "element-plus";
  264. import { getVoucherDetail, getHolidayList, addOrUpdateCoupon } from "@/api/modules/voucherManagement";
  265. import {
  266. validatePositiveNumber,
  267. validatePositiveInteger,
  268. validateDateRange,
  269. validateDateRangeArray,
  270. validateConditionalRequired,
  271. validateArrayMinLength,
  272. validateDateListArray,
  273. validatePriceFormat,
  274. validatePriceComparison
  275. } from "@/utils/eleValidate";
  276. import { localGet } from "@/utils";
  277. // ==================== 响应式数据定义 ====================
  278. // 路由相关
  279. const router = useRouter();
  280. const route = useRoute();
  281. // 页面状态
  282. const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
  283. const id = ref<string>(""); // 页面ID参数
  284. // ==================== 表单验证规则 ====================
  285. const rules = reactive({
  286. name: [{ required: true, message: "请输入代金券名称" }],
  287. price: [
  288. { required: true, message: "请输入抵扣价格" },
  289. {
  290. validator: validatePositiveNumber("抵扣价格必须为正数"),
  291. trigger: "blur"
  292. },
  293. {
  294. validator: (rule: any, value: any, callback: any) => {
  295. if (value === undefined || value === null || value === "") {
  296. callback();
  297. return;
  298. }
  299. const num = Number(value);
  300. if (isNaN(num) || num < 1) {
  301. callback(new Error("抵扣价格不能低于1元"));
  302. return;
  303. }
  304. callback();
  305. },
  306. trigger: "blur"
  307. },
  308. {
  309. validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
  310. trigger: "blur"
  311. },
  312. {
  313. validator: validatePriceComparison(
  314. () => voucherModel.value.price,
  315. () => voucherModel.value.offprice,
  316. "抵扣价格不能低于售卖价格"
  317. ),
  318. trigger: "blur"
  319. }
  320. ],
  321. offprice: [
  322. { required: true, message: "请输入售卖价格" },
  323. {
  324. validator: validatePositiveNumber("售卖价格必须为正数"),
  325. trigger: "blur"
  326. },
  327. {
  328. validator: (rule: any, value: any, callback: any) => {
  329. if (value === undefined || value === null || value === "") {
  330. callback();
  331. return;
  332. }
  333. const num = Number(value);
  334. if (isNaN(num) || num < 1) {
  335. callback(new Error("售卖价格不能低于1元"));
  336. return;
  337. }
  338. callback();
  339. },
  340. trigger: "blur"
  341. },
  342. {
  343. validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
  344. trigger: "blur"
  345. },
  346. {
  347. validator: validatePriceComparison(
  348. () => voucherModel.value.price,
  349. () => voucherModel.value.offprice,
  350. "抵扣价格不能低于售卖价格"
  351. ),
  352. trigger: "blur"
  353. }
  354. ],
  355. startDate: [
  356. { required: true, message: "请选择开始售卖时间" },
  357. {
  358. validator: (rule: any, value: any, callback: any) => {
  359. if (!value) {
  360. callback();
  361. return;
  362. }
  363. const selectedDate = new Date(value);
  364. const today = new Date();
  365. today.setHours(0, 0, 0, 0);
  366. // 验证不能早于今天
  367. if (selectedDate < today) {
  368. callback(new Error("开始售卖时间不能早于当前时间"));
  369. return;
  370. }
  371. // 验证开始时间不能晚于结束时间
  372. const endDate = voucherModel.value.endDate;
  373. if (endDate) {
  374. const end = new Date(endDate);
  375. if (selectedDate > end) {
  376. callback(new Error("开始售卖时间不能晚于结束售卖时间"));
  377. return;
  378. }
  379. }
  380. callback();
  381. },
  382. trigger: "change"
  383. }
  384. ],
  385. endDate: [
  386. { required: true, message: "请选择结束售卖时间" },
  387. {
  388. validator: (rule: any, value: any, callback: any) => {
  389. if (!value) {
  390. callback();
  391. return;
  392. }
  393. const selectedDate = new Date(value);
  394. const today = new Date();
  395. today.setHours(0, 0, 0, 0);
  396. // 验证不能早于今天
  397. if (selectedDate < today) {
  398. callback(new Error("结束售卖时间不能早于当前时间"));
  399. return;
  400. }
  401. // 验证结束时间不能早于开始时间
  402. const startDate = voucherModel.value.startDate;
  403. if (startDate) {
  404. const start = new Date(startDate);
  405. if (selectedDate < start) {
  406. callback(new Error("结束售卖时间不能早于开始售卖时间"));
  407. return;
  408. }
  409. }
  410. callback();
  411. },
  412. trigger: "change"
  413. }
  414. ],
  415. usageTime: [
  416. {
  417. required: true,
  418. validator: (rule: any, value: any, callback: any) => {
  419. if (!voucherModel.value.buyUseStartTime || !voucherModel.value.buyUseEndTime) {
  420. callback(new Error("请选择使用时间"));
  421. return;
  422. }
  423. callback();
  424. },
  425. trigger: []
  426. }
  427. ],
  428. expirationType: [{ required: true, message: "请选择有效期类型" }],
  429. expirationDate: [
  430. {
  431. required: true,
  432. validator: (rule: any, value: any, callback: any) => {
  433. if (voucherModel.value.expirationType == 1) {
  434. if (value === null || value === undefined || value === "") {
  435. callback(new Error("请输入用户购买天数"));
  436. return;
  437. }
  438. // 先验证是否为正整数
  439. validatePositiveInteger("用户购买天数必须为正整数", { required: false, checkLeadingZero: false })(
  440. rule,
  441. value,
  442. (error: any) => {
  443. if (error) {
  444. callback(error);
  445. return;
  446. }
  447. // 验证最大值
  448. const numValue = Number(value);
  449. if (!isNaN(numValue) && numValue > 10000) {
  450. callback(new Error("有效期不得大于10000"));
  451. return;
  452. }
  453. callback();
  454. }
  455. );
  456. } else {
  457. callback();
  458. }
  459. },
  460. trigger: "blur"
  461. }
  462. ],
  463. validityPeriod: [
  464. {
  465. required: true,
  466. validator: (rule: any, value: any, callback: any) => {
  467. if (voucherModel.value.expirationType == 2) {
  468. if (!value || value.length !== 2) {
  469. callback(new Error("请选择指定时间段"));
  470. return;
  471. }
  472. validateDateRangeArray("开始时间必须早于结束时间", true, "时间不能早于当前时间")(rule, value, callback);
  473. } else {
  474. callback();
  475. }
  476. },
  477. trigger: "change"
  478. }
  479. ],
  480. unusedType: [{ required: true, message: "请选择不可用日期类型" }],
  481. unavailableWeekdays: [
  482. {
  483. validator: (rule: any, value: any, callback: any) => {
  484. if (voucherModel.value.unusedType == 2) {
  485. const weekdays = voucherModel.value.unavailableWeekdays || [];
  486. const holidays = voucherModel.value.unavailableHolidays || [];
  487. if (weekdays.length === 0 && holidays.length === 0) {
  488. callback(new Error("至少需要选择一个星期或节日"));
  489. return;
  490. }
  491. }
  492. callback();
  493. },
  494. trigger: "change"
  495. }
  496. ],
  497. unavailableHolidays: [
  498. {
  499. validator: (rule: any, value: any, callback: any) => {
  500. if (voucherModel.value.unusedType == 2) {
  501. const weekdays = voucherModel.value.unavailableWeekdays || [];
  502. const holidays = voucherModel.value.unavailableHolidays || [];
  503. if (weekdays.length === 0 && holidays.length === 0) {
  504. callback(new Error("至少需要选择一个星期或节日"));
  505. return;
  506. }
  507. }
  508. callback();
  509. },
  510. trigger: "change"
  511. }
  512. ],
  513. customUnavailableDates: [
  514. {
  515. required: true,
  516. validator: (rule: any, value: any, callback: any) => {
  517. if (voucherModel.value.unusedType == 3) {
  518. if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
  519. callback(new Error("至少需要添加一个自定义不可用日期"));
  520. return;
  521. }
  522. validateDateListArray(
  523. () => voucherModel.value.disableDateList,
  524. "日期项未完整填写",
  525. "开始时间必须早于结束时间",
  526. true
  527. )(rule, value, callback);
  528. } else {
  529. callback();
  530. }
  531. },
  532. trigger: "change"
  533. }
  534. ],
  535. singleQty: [
  536. { required: true, message: "请输入库存数量" },
  537. {
  538. validator: (rule: any, value: any, callback: any) => {
  539. // 先验证是否为正整数
  540. validatePositiveInteger("库存必须为正整数", { required: false })(rule, value, (error: any) => {
  541. if (error) {
  542. callback(error);
  543. return;
  544. }
  545. // 验证最大值
  546. if (value) {
  547. const numValue = Number(value);
  548. if (!isNaN(numValue) && numValue > 10000) {
  549. callback(new Error("库存不得大于10000"));
  550. return;
  551. }
  552. }
  553. callback();
  554. });
  555. },
  556. trigger: "blur"
  557. }
  558. ],
  559. singleCanUseType: [{ required: true, message: "请选择单日可用数量类型" }],
  560. singleCanUse: [
  561. {
  562. required: true,
  563. validator: (rule: any, value: any, callback: any) => {
  564. if (voucherModel.value.singleCanUseType == 2) {
  565. if (!value || value.toString().trim() === "") {
  566. callback(new Error("请输入单日可用数量"));
  567. return;
  568. }
  569. // 验证是否为正整数
  570. validatePositiveInteger("单日可用数量必须为正整数", { required: false })(rule, value, (error: any) => {
  571. if (error) {
  572. callback(error);
  573. return;
  574. }
  575. const stock = voucherModel.value.singleQty;
  576. // 如果库存为空,不进行验证(由库存的验证规则处理)
  577. if (!stock || stock.toString().trim() === "") {
  578. callback();
  579. return;
  580. }
  581. const useNum = Number(value);
  582. const stockNum = Number(stock);
  583. // 如果转换失败,不进行验证(由其他验证规则处理)
  584. if (isNaN(useNum) || isNaN(stockNum)) {
  585. callback();
  586. return;
  587. }
  588. // 验证单日可用数量不能多于库存
  589. if (useNum > stockNum) {
  590. callback(new Error("单日可用数量不能多于库存"));
  591. return;
  592. }
  593. callback();
  594. });
  595. } else {
  596. callback();
  597. }
  598. },
  599. trigger: "blur"
  600. }
  601. ],
  602. purchaseLimitType: [{ required: true, message: "请选择限购数量类型" }],
  603. purchaseLimitCode: [
  604. {
  605. required: true,
  606. validator: (rule: any, value: any, callback: any) => {
  607. if (voucherModel.value.purchaseLimitType == 2) {
  608. if (!value || value.toString().trim() === "") {
  609. callback(new Error("请输入限购数量"));
  610. return;
  611. }
  612. // 验证是否为正整数
  613. validatePositiveInteger("限购数量必须为正整数", { required: false })(rule, value, (error: any) => {
  614. if (error) {
  615. callback(error);
  616. return;
  617. }
  618. const stock = voucherModel.value.singleQty;
  619. // 如果库存为空,不进行验证(由库存的验证规则处理)
  620. if (!stock || stock.toString().trim() === "") {
  621. callback();
  622. return;
  623. }
  624. const limitNum = Number(value);
  625. const stockNum = Number(stock);
  626. // 如果转换失败,不进行验证(由其他验证规则处理)
  627. if (isNaN(limitNum) || isNaN(stockNum)) {
  628. callback();
  629. return;
  630. }
  631. // 验证限购数量不能多于库存
  632. if (limitNum > stockNum) {
  633. callback(new Error("限购数量不能多于库存"));
  634. return;
  635. }
  636. callback();
  637. });
  638. } else {
  639. callback();
  640. }
  641. },
  642. trigger: "blur"
  643. }
  644. ],
  645. applyDesc: [
  646. {
  647. required: true,
  648. validator: (rule: any, value: any, callback: any) => {
  649. if (voucherModel.value.applyType == 2) {
  650. if (!value || value.toString().trim() === "") {
  651. callback(new Error("请输入适用范围"));
  652. return;
  653. }
  654. }
  655. callback();
  656. },
  657. trigger: "blur"
  658. }
  659. ]
  660. });
  661. // ==================== 代金券信息数据模型 ====================
  662. const voucherModel = ref<any>({
  663. // 代金券名称
  664. name: "",
  665. // 抵扣价格
  666. price: "",
  667. // 售卖价格
  668. offprice: "",
  669. // 开始售卖时间
  670. startDate: "",
  671. // 结束售卖时间
  672. endDate: "",
  673. // 使用时间 - 开始时间
  674. buyUseStartTime: "",
  675. // 使用时间 - 结束时间
  676. buyUseEndTime: "",
  677. // 使用时间(虚拟字段,用于表单验证)
  678. usageTime: null,
  679. // 有效期类型:1-指定天数,2-指定时间段内可用
  680. expirationType: "1",
  681. // 有效期天数(当expirationType为1时使用)
  682. expirationDate: 0,
  683. // 有效期时间段(当expirationType为2时使用)
  684. validityPeriod: [],
  685. // 不可用日期类型:1-全部日期可用,2-限制日期
  686. unusedType: "1",
  687. // 限制日期 - 星期选择(数组,存储选中的星期值)
  688. unavailableWeekdays: [],
  689. // 限制日期 - 节日选择(数组,存储选中的节日值)
  690. unavailableHolidays: [],
  691. // 自定义不可用日期列表(数组,每个元素是一个日期范围 [startDate, endDate])
  692. disableDateList: [],
  693. // 库存
  694. singleQty: "",
  695. // 单日可用数量类型:1-不限制,2-限制
  696. singleCanUseType: "1",
  697. // 单日可用数量
  698. singleCanUse: "",
  699. // 限购数量类型:1-不限制,2-限制
  700. purchaseLimitType: "1",
  701. // 限购数量
  702. purchaseLimitCode: "",
  703. // 适用范围类型:0-全场通用,1-部分不可用
  704. applyType: "1",
  705. // 适用范围(当applyType为1时使用)
  706. applyDesc: "",
  707. // 补充说明
  708. supplement: ""
  709. });
  710. // ==================== 下拉选项数据 ====================
  711. // 小时选项列表(0-24点)
  712. const hourOptions = ref(
  713. Array.from({ length: 25 }, (_, i) => ({
  714. value: String(i),
  715. label: `${i}点`
  716. }))
  717. );
  718. // 有效期类型列表
  719. const validityPeriodList = ref([
  720. { value: "1", label: "指定天数" },
  721. { value: "2", label: "指定时间段内可用" }
  722. ]);
  723. // 不可用日期类型列表
  724. const unavailableDateTypeList = ref([
  725. { value: "1", label: "全部日期可用" },
  726. { value: "2", label: "限制日期" }
  727. // { value: "3", label: "自定义不可用日期" }
  728. ]);
  729. // 适用范围类型列表
  730. const applicableScopeList = ref([
  731. { value: "1", label: "全场通用" },
  732. { value: "2", label: "部分不可用" }
  733. ]);
  734. // 单日可用数量类型列表
  735. const dailyUseLimitList = ref([
  736. { value: "1", label: "不限制" },
  737. { value: "2", label: "限制" }
  738. ]);
  739. // 限购数量类型列表
  740. const purchaseLimitList = ref([
  741. { value: "1", label: "不限制" },
  742. { value: "2", label: "限制" }
  743. ]);
  744. // 星期选项列表
  745. const weekdayList = ref([
  746. { name: "周一", id: "0", oName: "星期一" },
  747. { name: "周二", id: "1", oName: "星期二" },
  748. { name: "周三", id: "2", oName: "星期三" },
  749. { name: "周四", id: "3", oName: "星期四" },
  750. { name: "周五", id: "4", oName: "星期五" },
  751. { name: "周六", id: "5", oName: "星期六" },
  752. { name: "周日", id: "6", oName: "星期日" }
  753. ]);
  754. // 节日选项列表
  755. const holidayList: any = ref([]);
  756. // ==================== 监听器 ====================
  757. // 初始化标志,用于防止初始化时触发验证
  758. const isInitializing = ref(true);
  759. /**
  760. * 监听开始售卖时间变化
  761. * 当开始时间改变时,重新验证结束时间
  762. */
  763. watch(
  764. () => voucherModel.value.startDate,
  765. () => {
  766. if (isInitializing.value) return;
  767. if (voucherModel.value.endDate) {
  768. nextTick(() => {
  769. ruleFormRef.value?.validateField("endDate");
  770. });
  771. }
  772. }
  773. );
  774. /**
  775. * 监听结束售卖时间变化
  776. * 当结束时间改变时,重新验证开始时间
  777. */
  778. watch(
  779. () => voucherModel.value.endDate,
  780. () => {
  781. if (isInitializing.value) return;
  782. if (voucherModel.value.startDate) {
  783. nextTick(() => {
  784. ruleFormRef.value?.validateField("startDate");
  785. });
  786. }
  787. }
  788. );
  789. /**
  790. * 监听抵扣价格变化
  791. * 当抵扣价格改变时,重新验证售卖价格
  792. */
  793. watch(
  794. () => voucherModel.value.price,
  795. () => {
  796. if (isInitializing.value) return;
  797. if (voucherModel.value.offprice) {
  798. nextTick(() => {
  799. ruleFormRef.value?.validateField("offprice");
  800. });
  801. }
  802. }
  803. );
  804. /**
  805. * 监听售卖价格变化
  806. * 当售卖价格改变时,重新验证抵扣价格
  807. */
  808. watch(
  809. () => voucherModel.value.offprice,
  810. () => {
  811. if (isInitializing.value) return;
  812. if (voucherModel.value.price) {
  813. nextTick(() => {
  814. ruleFormRef.value?.validateField("price");
  815. });
  816. }
  817. }
  818. );
  819. /**
  820. * 监听库存变化
  821. * 当库存改变时,重新验证单日可用数量和限购数量
  822. */
  823. watch(
  824. () => voucherModel.value.singleQty,
  825. () => {
  826. if (isInitializing.value) return;
  827. if (voucherModel.value.singleCanUseType == 2 && voucherModel.value.singleCanUse) {
  828. nextTick(() => {
  829. ruleFormRef.value?.validateField("singleCanUse");
  830. });
  831. }
  832. if (voucherModel.value.purchaseLimitType == 2 && voucherModel.value.purchaseLimitCode) {
  833. nextTick(() => {
  834. ruleFormRef.value?.validateField("purchaseLimitCode");
  835. });
  836. }
  837. }
  838. );
  839. /**
  840. * 监听单日可用数量类型变化
  841. * 当切换到"不限制"时,清空输入框的值
  842. */
  843. watch(
  844. () => voucherModel.value.singleCanUseType,
  845. newVal => {
  846. if (newVal == 1) {
  847. voucherModel.value.singleCanUse = "";
  848. nextTick(() => {
  849. ruleFormRef.value?.clearValidate("singleCanUse");
  850. });
  851. }
  852. }
  853. );
  854. /**
  855. * 监听限购数量类型变化
  856. * 当切换到"不限制"时,清空输入框的值
  857. */
  858. watch(
  859. () => voucherModel.value.purchaseLimitType,
  860. newVal => {
  861. if (newVal == 1) {
  862. voucherModel.value.purchaseLimitCode = "";
  863. nextTick(() => {
  864. ruleFormRef.value?.clearValidate("purchaseLimitCode");
  865. });
  866. }
  867. }
  868. );
  869. /**
  870. * 监听适用范围类型变化
  871. * 当切换到"全场通用"时,清空输入框的值
  872. */
  873. watch(
  874. () => voucherModel.value.applyType,
  875. newVal => {
  876. if (newVal == 1) {
  877. voucherModel.value.applyDesc = "";
  878. nextTick(() => {
  879. ruleFormRef.value?.clearValidate("applyDesc");
  880. });
  881. }
  882. }
  883. );
  884. /**
  885. * 监听使用开始时间变化
  886. * 更新虚拟字段以支持表单验证
  887. */
  888. watch(
  889. () => voucherModel.value.buyUseStartTime,
  890. () => {
  891. // 更新虚拟字段
  892. voucherModel.value.usageTime =
  893. voucherModel.value.buyUseStartTime && voucherModel.value.buyUseEndTime
  894. ? [voucherModel.value.buyUseStartTime, voucherModel.value.buyUseEndTime]
  895. : null;
  896. }
  897. );
  898. /**
  899. * 监听使用结束时间变化
  900. * 更新虚拟字段以支持表单验证
  901. */
  902. watch(
  903. () => voucherModel.value.buyUseEndTime,
  904. () => {
  905. // 更新虚拟字段
  906. voucherModel.value.usageTime =
  907. voucherModel.value.buyUseStartTime && voucherModel.value.buyUseEndTime
  908. ? [voucherModel.value.buyUseStartTime, voucherModel.value.buyUseEndTime]
  909. : null;
  910. }
  911. );
  912. /**
  913. * 监听不可用日期类型变化
  914. * 当切换到自定义不可用日期时,确保至少有一个日期项
  915. */
  916. watch(
  917. () => voucherModel.value.unusedType,
  918. newVal => {
  919. if (newVal == 3) {
  920. // 切换到自定义不可用日期时,如果disableDateList为空,则添加一个默认项
  921. if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
  922. voucherModel.value.disableDateList = [null];
  923. }
  924. }
  925. },
  926. { immediate: true }
  927. );
  928. // ==================== 生命周期钩子 ====================
  929. /**
  930. * 组件挂载时初始化
  931. * 从路由参数中获取页面类型和ID
  932. */
  933. onMounted(async () => {
  934. id.value = (route.query.id as string) || "";
  935. type.value = (route.query.type as string) || "";
  936. // 加载节日列表
  937. let params = {
  938. year: new Date().getFullYear(),
  939. page: 1,
  940. size: 500,
  941. openFlag: 1,
  942. holidayName: ""
  943. };
  944. let res: any = await getHolidayList(params);
  945. if (res && res.code == 200) {
  946. holidayList.value = res.data.records;
  947. }
  948. // 编辑模式下加载数据
  949. if (type.value != "add") {
  950. let res: any = await getVoucherDetail({ id: id.value });
  951. voucherModel.value = { ...voucherModel.value, ...res.data };
  952. // 处理有效期时间段:将时间戳字符串转换为数字数组
  953. if (voucherModel.value.validityPeriod && voucherModel.value.expirationType == 2) {
  954. const periodArray = voucherModel.value.validityPeriod.split(",");
  955. voucherModel.value.validityPeriod = periodArray
  956. .map((item: string) => Number(item.trim()))
  957. .filter((item: number) => !isNaN(item));
  958. } else {
  959. voucherModel.value.validityPeriod = [];
  960. }
  961. // 确保星期和节日字段存在;
  962. if (voucherModel.value.unusedType == 2) {
  963. const listVal = voucherModel.value.unusedDate ? voucherModel.value.unusedDate.split(";") : [];
  964. voucherModel.value.unavailableWeekdays = listVal[0] ? listVal[0].split(",").filter((item: string) => item) : [];
  965. voucherModel.value.unavailableHolidays = listVal[1] ? listVal[1].split(",").filter((item: string) => item) : [];
  966. }
  967. // 确保自定义不可用日期字段存在;
  968. if (voucherModel.value.unusedType === 3) {
  969. if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
  970. voucherModel.value.disableDateList = [null];
  971. }
  972. }
  973. // 处理单日可用数量类型:如果有值,设置为"限制",否则设置为"不限制"
  974. if (
  975. voucherModel.value.singleCanUse !== null &&
  976. voucherModel.value.singleCanUse !== undefined &&
  977. voucherModel.value.singleCanUse.toString().trim() !== ""
  978. ) {
  979. voucherModel.value.singleCanUseType = "2";
  980. } else {
  981. voucherModel.value.singleCanUseType = "1";
  982. voucherModel.value.singleCanUse = "";
  983. }
  984. // 处理限购数量类型:如果有值,设置为"限制",否则设置为"不限制"
  985. if (
  986. voucherModel.value.purchaseLimitCode !== null &&
  987. voucherModel.value.purchaseLimitCode !== undefined &&
  988. voucherModel.value.purchaseLimitCode.toString().trim() !== ""
  989. ) {
  990. voucherModel.value.purchaseLimitType = "2";
  991. } else {
  992. voucherModel.value.purchaseLimitType = "1";
  993. voucherModel.value.purchaseLimitCode = "";
  994. }
  995. // 处理适用范围类型:如果有值,设置为"部分不可用",否则设置为"全场通用"
  996. if (
  997. voucherModel.value.applyDesc !== null &&
  998. voucherModel.value.applyDesc !== undefined &&
  999. voucherModel.value.applyDesc.toString().trim() !== ""
  1000. ) {
  1001. voucherModel.value.applyType = "2";
  1002. } else {
  1003. voucherModel.value.applyType = "1";
  1004. voucherModel.value.applyDesc = "";
  1005. }
  1006. console.log(voucherModel.value);
  1007. }
  1008. await nextTick();
  1009. ruleFormRef.value?.clearValidate();
  1010. isInitializing.value = false;
  1011. });
  1012. // ==================== 事件处理函数 ====================
  1013. /**
  1014. * 返回上一页
  1015. */
  1016. const goBack = () => {
  1017. router.go(-1);
  1018. };
  1019. /**
  1020. * 切换星期选择
  1021. * @param value 星期值
  1022. */
  1023. const toggleWeekday = (value: string) => {
  1024. if (!voucherModel.value.unavailableWeekdays) {
  1025. voucherModel.value.unavailableWeekdays = [];
  1026. }
  1027. const index = voucherModel.value.unavailableWeekdays.indexOf(value);
  1028. if (index > -1) {
  1029. voucherModel.value.unavailableWeekdays.splice(index, 1);
  1030. } else {
  1031. voucherModel.value.unavailableWeekdays.push(value);
  1032. }
  1033. // 触发表单验证(同时验证星期和节日字段)
  1034. nextTick(() => {
  1035. ruleFormRef.value?.validateField("unavailableWeekdays");
  1036. ruleFormRef.value?.validateField("unavailableHolidays");
  1037. });
  1038. };
  1039. /**
  1040. * 切换节日选择
  1041. * @param value 节日值
  1042. */
  1043. const toggleHoliday = (value: string | number) => {
  1044. if (!voucherModel.value.unavailableHolidays) {
  1045. voucherModel.value.unavailableHolidays = [];
  1046. }
  1047. // 统一转换为字符串进行比较
  1048. const valueStr = String(value);
  1049. const index = voucherModel.value.unavailableHolidays.findIndex((item: any) => String(item) === valueStr);
  1050. if (index > -1) {
  1051. voucherModel.value.unavailableHolidays.splice(index, 1);
  1052. } else {
  1053. voucherModel.value.unavailableHolidays.push(valueStr);
  1054. }
  1055. // 触发表单验证(同时验证星期和节日字段)
  1056. nextTick(() => {
  1057. ruleFormRef.value?.validateField("unavailableWeekdays");
  1058. ruleFormRef.value?.validateField("unavailableHolidays");
  1059. });
  1060. };
  1061. /**
  1062. * 添加自定义不可用日期
  1063. */
  1064. const addDate = () => {
  1065. if (!voucherModel.value.disableDateList) {
  1066. voucherModel.value.disableDateList = [];
  1067. }
  1068. voucherModel.value.disableDateList.push(null);
  1069. };
  1070. /**
  1071. * 删除自定义不可用日期
  1072. * @param index 要删除的日期索引
  1073. */
  1074. const removeDate = (index: number) => {
  1075. if (voucherModel.value.disableDateList.length <= 1) {
  1076. ElMessage.warning("至少需要保留一个日期项");
  1077. return;
  1078. }
  1079. voucherModel.value.disableDateList.splice(index, 1);
  1080. // 删除日期项后,重新验证表单以清除旧的验证错误
  1081. nextTick(() => {
  1082. ruleFormRef.value?.validateField("customUnavailableDates");
  1083. });
  1084. };
  1085. // ==================== 表单引用 ====================
  1086. const ruleFormRef = ref<FormInstance>(); // 表单引用
  1087. /**
  1088. * 提交数据(新增/编辑)
  1089. * 验证表单,通过后调用相应的API接口
  1090. */
  1091. const handleSubmit = async (submitType?: string) => {
  1092. // 组装提交参数
  1093. let params: any = { ...voucherModel.value };
  1094. params.storeId = localGet("createdId");
  1095. params.status = 1;
  1096. // 处理有效期:只有当expirationType为2(指定时间段内可用)时才处理validityPeriod
  1097. if (params.expirationType == 2 && params.validityPeriod && Array.isArray(params.validityPeriod)) {
  1098. params.validityPeriod = params.validityPeriod.join(",");
  1099. } else if (params.expirationType == 1) {
  1100. // 指定天数模式,不需要validityPeriod字段
  1101. params.validityPeriod = "";
  1102. }
  1103. // 处理不可用日期
  1104. if (params.unusedType == 2) {
  1105. // 对 unavailableWeekdays 按照 weekdayList 中的索引顺序排序
  1106. if (params.unavailableWeekdays && params.unavailableWeekdays.length > 0) {
  1107. params.unavailableWeekdays = params.unavailableWeekdays.sort((a: string, b: string) => {
  1108. const indexA = weekdayList.value.findIndex(item => item.oName === a);
  1109. const indexB = weekdayList.value.findIndex(item => item.oName === b);
  1110. return indexA - indexB;
  1111. });
  1112. }
  1113. // 对 unavailableHolidays 按照 holidayList 中的索引顺序排序
  1114. if (params.unavailableHolidays && params.unavailableHolidays.length > 0) {
  1115. params.unavailableHolidays = params.unavailableHolidays.sort((a: string | number, b: string | number) => {
  1116. const indexA = holidayList.value.findIndex(item => String(item.id) === String(a));
  1117. const indexB = holidayList.value.findIndex(item => String(item.id) === String(b));
  1118. return indexA - indexB;
  1119. });
  1120. }
  1121. // 节日名称 = 用 ID 到 holidayList 中映射
  1122. const holidayNames = (params.unavailableHolidays || [])
  1123. .map(id => {
  1124. const hit = holidayList.value.find(item => item.id == id);
  1125. return hit ? hit.festivalName : null;
  1126. })
  1127. .filter(Boolean);
  1128. const holidayIds = (params.unavailableHolidays || [])
  1129. .map(id => {
  1130. const hit = holidayList.value.find(item => item.id == id);
  1131. return hit ? hit.id : null;
  1132. })
  1133. .filter(Boolean);
  1134. // 组装 unusedDate(星期名称 + 节日名称)并去重
  1135. const unusedDate = [...new Set([...(params.unavailableWeekdays || []), ...holidayNames])];
  1136. params.unusedDate = unusedDate.join(",");
  1137. // // 保存选中的节日ID到 useFestival 字段
  1138. params.useFestival = (params.unavailableHolidays || []).join(",");
  1139. params.unavaiLableDate = params.unavailableWeekdays.join(",") + ";" + holidayIds.join(",");
  1140. } else if (params.unusedType == 3) {
  1141. // 处理自定义不可用日期
  1142. if (params.disableDateList && params.disableDateList.length > 0) {
  1143. params.unusedDate = params.disableDateList
  1144. .map((dateRange: any) => (dateRange && dateRange.length === 2 ? dateRange.join(",") : ""))
  1145. .filter((item: string) => item)
  1146. .join(";");
  1147. }
  1148. }
  1149. // 处理单日可用数量:如果选择"不限制",清空值
  1150. if (params.singleCanUseType == 1) {
  1151. params.singleCanUse = "";
  1152. }
  1153. // 处理限购数量:如果选择"不限制",清空值
  1154. if (params.purchaseLimitType == 1) {
  1155. params.purchaseLimitCode = "";
  1156. }
  1157. params.dataType = submitType ? 1 : 0;
  1158. // 是否可叠加 不知道有什么用 预留
  1159. params.stackingType = 0;
  1160. // 是否全场通用 不知道有什么用 预留
  1161. params.generalType = 0;
  1162. delete params.unavailableWeekdays;
  1163. delete params.unavailableHolidays;
  1164. delete params.disableDateList;
  1165. delete params.singleCanUseType;
  1166. delete params.purchaseLimitType;
  1167. console.log("提交参数:", params);
  1168. if (submitType) {
  1169. if (!voucherModel.value.name) {
  1170. ElMessage.warning("请填写代金券名称");
  1171. return;
  1172. }
  1173. let res: any = await addOrUpdateCoupon(params);
  1174. if (res && res.code == 200) {
  1175. ElMessage.success("保存成功");
  1176. goBack();
  1177. }
  1178. return;
  1179. }
  1180. // 验证表单
  1181. ruleFormRef.value!.validate(async (valid: boolean) => {
  1182. if (!valid) return;
  1183. let res: any = await addOrUpdateCoupon(params);
  1184. if (res && res.code == 200) {
  1185. ElMessage.success("创建成功,请耐心等待审核");
  1186. goBack();
  1187. }
  1188. });
  1189. };
  1190. // ==================== 工具函数 ====================
  1191. /**
  1192. * 禁用开始售卖时间的日期
  1193. * 不能选择早于当前时间的日期
  1194. */
  1195. const disabledStartDate = (time: Date) => {
  1196. const today = new Date();
  1197. today.setHours(0, 0, 0, 0);
  1198. return time.getTime() < today.getTime();
  1199. };
  1200. /**
  1201. * 禁用结束售卖时间的日期
  1202. * 不能选择早于当前时间的日期,也不能选择早于或等于开始售卖时间的日期
  1203. */
  1204. const disabledEndDate = (time: Date) => {
  1205. const today = new Date();
  1206. today.setHours(0, 0, 0, 0);
  1207. if (time.getTime() < today.getTime()) {
  1208. return true;
  1209. }
  1210. if (voucherModel.value.startDate) {
  1211. const startDate = new Date(voucherModel.value.startDate);
  1212. startDate.setHours(0, 0, 0, 0);
  1213. return time.getTime() < startDate.getTime();
  1214. }
  1215. return false;
  1216. };
  1217. /**
  1218. * 禁用有效期时间段的日期
  1219. * 不能选择早于当前时间的日期
  1220. * @param time 日期对象
  1221. * @returns 是否禁用该日期
  1222. */
  1223. const disabledValidityDate = (time: Date) => {
  1224. const today = new Date();
  1225. today.setHours(0, 0, 0, 0);
  1226. return time.getTime() < today.getTime();
  1227. };
  1228. /**
  1229. * 禁用自定义不可用日期的日期
  1230. * 不能选择早于当前时间的日期
  1231. * @param time 日期对象
  1232. * @returns 是否禁用该日期
  1233. */
  1234. const disabledCustomUnavailableDate = (time: Date) => {
  1235. const today = new Date();
  1236. today.setHours(0, 0, 0, 0);
  1237. return time.getTime() < today.getTime();
  1238. };
  1239. </script>
  1240. <style scoped lang="scss">
  1241. /* 页面容器 */
  1242. .table-box {
  1243. display: flex;
  1244. flex-direction: column;
  1245. height: auto !important;
  1246. min-height: 100%;
  1247. }
  1248. /* 头部区域 */
  1249. .header {
  1250. display: flex;
  1251. align-items: center;
  1252. padding: 20px;
  1253. border-bottom: 1px solid #e4e7ed;
  1254. }
  1255. .title {
  1256. flex: 1;
  1257. margin: 0;
  1258. font-size: 18px;
  1259. font-weight: bold;
  1260. text-align: center;
  1261. }
  1262. /* 内容区域布局 */
  1263. .content {
  1264. display: flex;
  1265. flex: 1;
  1266. column-gap: 20px;
  1267. width: 98%;
  1268. margin: 20px auto;
  1269. /* 左侧内容区域 */
  1270. .contentLeft {
  1271. width: 50%;
  1272. }
  1273. /* 右侧内容区域 */
  1274. .contentRight {
  1275. width: 50%;
  1276. }
  1277. }
  1278. /* 模块容器 */
  1279. .model {
  1280. margin-bottom: 50px;
  1281. }
  1282. /* 表单容器 */
  1283. .formBox {
  1284. display: flex;
  1285. flex-direction: column;
  1286. width: 100%;
  1287. min-height: 100%;
  1288. }
  1289. /* 底部按钮容器 - 居中显示 */
  1290. .button-container {
  1291. display: flex;
  1292. gap: 12px;
  1293. align-items: center;
  1294. justify-content: center;
  1295. padding: 20px 0;
  1296. margin-top: 20px;
  1297. }
  1298. /* 有效期天数容器 */
  1299. .expiration-date-container {
  1300. display: flex;
  1301. gap: 12px;
  1302. align-items: center;
  1303. width: fit-content;
  1304. padding: 8px 12px;
  1305. border-radius: 4px;
  1306. }
  1307. /* 有效期标签文字 */
  1308. .expiration-label {
  1309. font-size: 14px;
  1310. color: #606266;
  1311. white-space: nowrap;
  1312. }
  1313. /* 有效期输入框 */
  1314. .expiration-input {
  1315. width: 150px;
  1316. }
  1317. /* 不可用日期容器 */
  1318. .unavailable-dates-container {
  1319. display: flex;
  1320. flex-direction: column;
  1321. gap: 20px;
  1322. width: 100%;
  1323. }
  1324. /* 日期选择区块 */
  1325. .date-select-section {
  1326. display: flex;
  1327. flex-direction: column;
  1328. gap: 12px;
  1329. }
  1330. /* 区块标题 */
  1331. .section-title {
  1332. font-size: 14px;
  1333. font-weight: 500;
  1334. color: #606266;
  1335. }
  1336. /* 按钮组 */
  1337. .button-group {
  1338. display: flex;
  1339. flex-wrap: wrap;
  1340. gap: 10px;
  1341. }
  1342. /* 日期选择按钮 */
  1343. .date-select-btn {
  1344. min-width: 80px;
  1345. height: 36px;
  1346. padding: 8px 16px;
  1347. margin: 0;
  1348. font-size: 14px;
  1349. border-radius: 4px;
  1350. transition: all 0.1s;
  1351. }
  1352. /* 日期选择器容器 */
  1353. .date-picker-container {
  1354. display: flex;
  1355. flex-direction: column;
  1356. gap: 12px;
  1357. width: 100%;
  1358. }
  1359. /* 添加日期按钮 */
  1360. .add-date-btn {
  1361. width: fit-content;
  1362. margin-bottom: 8px;
  1363. }
  1364. /* 日期项容器 */
  1365. .date-item {
  1366. display: flex;
  1367. gap: 12px;
  1368. align-items: center;
  1369. padding: 8px;
  1370. border-radius: 4px;
  1371. transition: background-color 0.1s;
  1372. &:hover {
  1373. background-color: #ecf5ff;
  1374. }
  1375. }
  1376. /* 日期选择器 */
  1377. .date-item .date-picker {
  1378. flex: 1;
  1379. max-width: 500px;
  1380. }
  1381. /* 删除按钮 */
  1382. .date-item .delete-btn {
  1383. flex-shrink: 0;
  1384. }
  1385. /* 时间范围容器 */
  1386. .time-range-container {
  1387. display: flex;
  1388. gap: 12px;
  1389. align-items: center;
  1390. width: 100%;
  1391. }
  1392. /* 时间选择器 */
  1393. .time-picker {
  1394. flex: 1;
  1395. max-width: 200px;
  1396. }
  1397. /* 时间分隔符 */
  1398. .time-separator {
  1399. font-size: 14px;
  1400. color: #606266;
  1401. white-space: nowrap;
  1402. }
  1403. </style>