sale-time.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. <template>
  2. <view class="page">
  3. <u-navbar title="售卖时间" :autoBack="true" :placeholder="true"></u-navbar>
  4. <view class="card">
  5. <view class="label required">菜品售卖日期</view>
  6. <u-radio-group v-model="mode" @change="onModeChange">
  7. <u-radio name="fixed" active-color="#449AFF">固定日期</u-radio>
  8. <u-radio name="custom" active-color="#449AFF">自定义</u-radio>
  9. <u-radio v-if="fromMulti" name="all" active-color="#449AFF">全部日期售卖</u-radio>
  10. </u-radio-group>
  11. </view>
  12. <!-- 固定日期:月历多选 -->
  13. <view class="card" v-if="mode === 'fixed'">
  14. <view class="cal-head">
  15. <view class="cal-month" @click="monthPickerShow = true">
  16. <text>{{ year }}年{{ month }}月</text>
  17. <u-icon name="arrow-down" color="#333" size="24"></u-icon>
  18. </view>
  19. <view class="cal-all" @click="toggleSelectAll">
  20. <text>全选</text>
  21. <view :class="['check', isMonthAllSelected ? 'on' : '']">
  22. <u-icon v-if="isMonthAllSelected" name="checkmark" color="#fff" size="18"></u-icon>
  23. </view>
  24. </view>
  25. </view>
  26. <view class="week">
  27. <text v-for="w in weeks" :key="w">{{ w }}</text>
  28. </view>
  29. <view class="days">
  30. <view
  31. v-for="(d, i) in days"
  32. :key="i"
  33. :class="['day', !d ? 'empty' : '', d && selectedDates.includes(d.full) ? 'on' : '']"
  34. @click="toggleDate(d)"
  35. >
  36. <text v-if="d" class="n">{{ d.day }}</text>
  37. </view>
  38. </view>
  39. </view>
  40. <!-- 自定义区间 -->
  41. <view class="card" v-if="mode === 'custom'">
  42. <view class="label">自定义时间</view>
  43. <view class="range-row">
  44. <view class="range-box" @click="openRangePicker('start')">
  45. {{ startDate || '开始日期' }}
  46. </view>
  47. <text class="range-sep">-</text>
  48. <view class="range-box" @click="openRangePicker('end')">
  49. {{ endDate || '结束日期' }}
  50. </view>
  51. </view>
  52. </view>
  53. <view class="card tip" v-if="mode === 'all'">
  54. 商品上架后,在售卖期内每日均可售卖;超出日期后自动对用户端隐藏。
  55. </view>
  56. <view class="footer">
  57. <view class="f-btn outline" @click="goPrev">上一步</view>
  58. <view
  59. :class="['f-btn', 'primary', !canSubmit || submitting ? 'disabled' : '']"
  60. @click="onSubmit"
  61. >
  62. 提交审核
  63. </view>
  64. </view>
  65. <!-- 年月切换 -->
  66. <view class="pick-mask" v-if="monthPickerShow" @click="monthPickerShow = false">
  67. <view class="pick-sheet" @click.stop>
  68. <view class="pick-title">选择月份</view>
  69. <picker-view class="pick-view" :value="monthPickerValue" @change="onMonthPickerChange">
  70. <picker-view-column>
  71. <view class="pick-item" v-for="y in yearList" :key="y">{{ y }}年</view>
  72. </picker-view-column>
  73. <picker-view-column>
  74. <view class="pick-item" v-for="m in 12" :key="m">{{ m }}月</view>
  75. </picker-view-column>
  76. </picker-view>
  77. <view class="pick-confirm" @click="confirmMonthPicker">确定</view>
  78. </view>
  79. </view>
  80. <!-- 自定义:选择时间(起止) -->
  81. <view class="pick-mask" v-if="rangePickerShow" @click="rangePickerShow = false">
  82. <view class="pick-sheet" @click.stop>
  83. <view class="pick-title">选择时间</view>
  84. <view class="range-preview">
  85. <view
  86. :class="['preview-box', rangeField === 'start' ? 'on' : '']"
  87. @click="switchRangeField('start')"
  88. >
  89. {{ formatCnDate(tempStart) || '开始日期' }}
  90. </view>
  91. <view
  92. :class="['preview-box', rangeField === 'end' ? 'on' : '']"
  93. @click="switchRangeField('end')"
  94. >
  95. {{ formatCnDate(tempEnd) || '结束日期' }}
  96. </view>
  97. </view>
  98. <picker-view class="pick-view" :value="datePickerValue" @change="onDatePickerChange">
  99. <picker-view-column>
  100. <view class="pick-item" v-for="y in yearList" :key="'dy' + y">{{ y }}年</view>
  101. </picker-view-column>
  102. <picker-view-column>
  103. <view class="pick-item" v-for="m in 12" :key="'dm' + m">{{ m }}月</view>
  104. </picker-view-column>
  105. <picker-view-column>
  106. <view class="pick-item" v-for="d in dayList" :key="'dd' + d">{{ d }}日</view>
  107. </picker-view-column>
  108. </picker-view>
  109. <view class="pick-confirm" @click="confirmRangePicker">确定</view>
  110. </view>
  111. </view>
  112. </view>
  113. </template>
  114. <script>
  115. import merchantDishApi, {
  116. belongingToApi,
  117. COMBO_CAT_TO_API,
  118. getMerchantId,
  119. PRODUCT_TYPE_TO_CATEGORY
  120. } from '@/api/merchantDish.js';
  121. import session from '../session.js';
  122. export default {
  123. data() {
  124. const now = new Date();
  125. const year = now.getFullYear();
  126. const yearList = [];
  127. for (let y = year - 2; y <= year + 10; y++) yearList.push(y);
  128. return {
  129. fromMulti: false,
  130. mode: 'fixed',
  131. year,
  132. month: now.getMonth() + 1,
  133. weeks: ['日', '一', '二', '三', '四', '五', '六'],
  134. selectedDates: [],
  135. startDate: '',
  136. endDate: '',
  137. submitting: false,
  138. yearList,
  139. monthPickerShow: false,
  140. monthPickerValue: [2, now.getMonth()],
  141. rangePickerShow: false,
  142. rangeField: 'start',
  143. tempStart: '',
  144. tempEnd: '',
  145. datePickerValue: [2, now.getMonth(), now.getDate() - 1],
  146. pickerDay: now.getDate()
  147. };
  148. },
  149. computed: {
  150. days() {
  151. const first = new Date(this.year, this.month - 1, 1);
  152. const startWeek = first.getDay();
  153. const total = new Date(this.year, this.month, 0).getDate();
  154. const arr = [];
  155. for (let i = 0; i < startWeek; i++) arr.push(null);
  156. for (let d = 1; d <= total; d++) {
  157. const m = String(this.month).padStart(2, '0');
  158. const day = String(d).padStart(2, '0');
  159. arr.push({ day: d, full: `${this.year}-${m}-${day}` });
  160. }
  161. return arr;
  162. },
  163. monthDateList() {
  164. return this.days.filter(Boolean).map((d) => d.full);
  165. },
  166. isMonthAllSelected() {
  167. const list = this.monthDateList;
  168. if (!list.length) return false;
  169. return list.every((d) => this.selectedDates.includes(d));
  170. },
  171. dayList() {
  172. const y =
  173. this.yearList[this.datePickerValue[0]] ||
  174. this.year;
  175. const m = (this.datePickerValue[1] || 0) + 1;
  176. const total = new Date(y, m, 0).getDate();
  177. const arr = [];
  178. for (let d = 1; d <= total; d++) arr.push(d);
  179. return arr;
  180. },
  181. canSubmit() {
  182. if (this.mode === 'all') return true;
  183. if (this.mode === 'fixed') return this.selectedDates.length > 0;
  184. return !!(this.startDate && this.endDate);
  185. }
  186. },
  187. onLoad(query) {
  188. this.fromMulti = query.from === 'multi';
  189. if (this.fromMulti) this.mode = 'fixed';
  190. this.syncMonthPickerValue();
  191. this.hydrateFromEditExtra();
  192. },
  193. methods: {
  194. normalizeDate(val) {
  195. if (!val) return '';
  196. const s = String(val).trim();
  197. const m = s.match(/^(\d{4}-\d{2}-\d{2})/);
  198. return m ? m[1] : s.slice(0, 10);
  199. },
  200. formatCnDate(val) {
  201. const d = this.normalizeDate(val);
  202. if (!d) return '';
  203. const [y, m, day] = d.split('-');
  204. return `${Number(y)}年${Number(m)}月${Number(day)}日`;
  205. },
  206. toYmd(y, m, d) {
  207. return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
  208. },
  209. /** 编辑回显:优先根据 saleTimes 内容推断 */
  210. hydrateFromEditExtra() {
  211. const extra = session.getDishEditExtra() || {};
  212. const saleTimes = Array.isArray(extra.saleTimes) ? extra.saleTimes : [];
  213. const saleDateType = Number(extra.saleDateType);
  214. if (!saleTimes.length && !saleDateType) return;
  215. const range = saleTimes.find(
  216. (t) => Number(t.saleType) === 2 || t.startDate || t.endDate
  217. );
  218. if (range && (range.startDate || range.endDate)) {
  219. this.mode = 'custom';
  220. this.startDate = this.normalizeDate(range.startDate);
  221. this.endDate = this.normalizeDate(range.endDate);
  222. return;
  223. }
  224. const fixedDates = saleTimes
  225. .filter((t) => Number(t.saleType) === 1 || t.saleDate)
  226. .map((t) => this.normalizeDate(t.saleDate))
  227. .filter(Boolean)
  228. .sort();
  229. if (fixedDates.length || saleDateType === 1) {
  230. this.mode = 'fixed';
  231. this.selectedDates = fixedDates;
  232. if (fixedDates[0]) {
  233. const [y, m] = fixedDates[0].split('-').map(Number);
  234. if (y) this.year = y;
  235. if (m) this.month = m;
  236. this.syncMonthPickerValue();
  237. }
  238. return;
  239. }
  240. if ((saleDateType === 2 || !saleTimes.length) && this.fromMulti) {
  241. this.mode = 'all';
  242. }
  243. },
  244. syncMonthPickerValue() {
  245. const yi = this.yearList.indexOf(this.year);
  246. this.monthPickerValue = [yi >= 0 ? yi : 0, this.month - 1];
  247. },
  248. onModeChange() {
  249. this.selectedDates = [];
  250. this.startDate = '';
  251. this.endDate = '';
  252. },
  253. toggleDate(d) {
  254. if (!d) return;
  255. const i = this.selectedDates.indexOf(d.full);
  256. if (i >= 0) this.selectedDates.splice(i, 1);
  257. else this.selectedDates.push(d.full);
  258. },
  259. toggleSelectAll() {
  260. const list = this.monthDateList;
  261. if (this.isMonthAllSelected) {
  262. this.selectedDates = this.selectedDates.filter((d) => !list.includes(d));
  263. } else {
  264. const set = new Set(this.selectedDates.concat(list));
  265. this.selectedDates = Array.from(set).sort();
  266. }
  267. },
  268. onMonthPickerChange(e) {
  269. this.monthPickerValue = e.detail.value || [0, 0];
  270. },
  271. confirmMonthPicker() {
  272. const [yi, mi] = this.monthPickerValue;
  273. this.year = this.yearList[yi] || this.year;
  274. this.month = (mi || 0) + 1;
  275. this.monthPickerShow = false;
  276. },
  277. openRangePicker(field) {
  278. this.rangeField = field || 'start';
  279. this.tempStart = this.startDate;
  280. this.tempEnd = this.endDate;
  281. const base = this.normalizeDate(
  282. field === 'end' ? this.endDate || this.startDate : this.startDate || this.endDate
  283. );
  284. this.applyDateToPicker(base);
  285. // 首次打开且对应字段为空时,用当前滚轮日期预填
  286. const now = new Date();
  287. const fallback = this.toYmd(now.getFullYear(), now.getMonth() + 1, now.getDate());
  288. const seed = base || fallback;
  289. if (field === 'start' && !this.tempStart) this.tempStart = seed;
  290. if (field === 'end' && !this.tempEnd) this.tempEnd = seed;
  291. this.rangePickerShow = true;
  292. },
  293. switchRangeField(field) {
  294. this.rangeField = field;
  295. const base = this.normalizeDate(field === 'start' ? this.tempStart : this.tempEnd);
  296. this.applyDateToPicker(base);
  297. },
  298. applyDateToPicker(ymd) {
  299. const now = new Date();
  300. let y = now.getFullYear();
  301. let m = now.getMonth() + 1;
  302. let d = now.getDate();
  303. if (ymd) {
  304. const parts = ymd.split('-').map(Number);
  305. y = parts[0] || y;
  306. m = parts[1] || m;
  307. d = parts[2] || d;
  308. }
  309. let yi = this.yearList.indexOf(y);
  310. if (yi < 0) yi = 0;
  311. const total = new Date(y, m, 0).getDate();
  312. if (d > total) d = total;
  313. this.pickerDay = d;
  314. this.datePickerValue = [yi, m - 1, d - 1];
  315. this.$nextTick(() => {
  316. this.datePickerValue = [yi, m - 1, d - 1];
  317. });
  318. },
  319. onDatePickerChange(e) {
  320. const val = e.detail.value || [0, 0, 0];
  321. const y = this.yearList[val[0]] || this.year;
  322. const m = (val[1] || 0) + 1;
  323. const total = new Date(y, m, 0).getDate();
  324. let d = (val[2] || 0) + 1;
  325. if (d > total) d = total;
  326. this.pickerDay = d;
  327. this.datePickerValue = [val[0], val[1], d - 1];
  328. const ymd = this.toYmd(y, m, d);
  329. if (this.rangeField === 'start') this.tempStart = ymd;
  330. else this.tempEnd = ymd;
  331. },
  332. confirmRangePicker() {
  333. // 滚轮停住后若未触发 change,用当前索引兜底写入当前字段
  334. const [yi, mi, di] = this.datePickerValue;
  335. const y = this.yearList[yi] || this.year;
  336. const m = (mi || 0) + 1;
  337. const total = new Date(y, m, 0).getDate();
  338. let d = (di || 0) + 1;
  339. if (d > total) d = total;
  340. const ymd = this.toYmd(y, m, d);
  341. if (this.rangeField === 'start') {
  342. if (!this.tempStart) this.tempStart = ymd;
  343. } else if (!this.tempEnd) {
  344. this.tempEnd = ymd;
  345. }
  346. if (!this.tempStart || !this.tempEnd) {
  347. uni.showToast({ title: '请选择开始和结束日期', icon: 'none' });
  348. return;
  349. }
  350. if (this.tempStart > this.tempEnd) {
  351. uni.showToast({ title: '开始日期不能晚于结束日期', icon: 'none' });
  352. return;
  353. }
  354. this.startDate = this.tempStart;
  355. this.endDate = this.tempEnd;
  356. this.rangePickerShow = false;
  357. },
  358. goPrev() {
  359. uni.navigateBack();
  360. },
  361. buildSaleTimes() {
  362. if (this.mode === 'fixed') {
  363. return {
  364. saleDateType: 1,
  365. saleTimes: this.selectedDates
  366. .slice()
  367. .sort()
  368. .map((d, i) => ({
  369. saleType: 1,
  370. saleDate: d,
  371. sort: i
  372. }))
  373. };
  374. }
  375. if (this.mode === 'custom') {
  376. return {
  377. saleDateType: 2,
  378. saleTimes: [
  379. {
  380. saleType: 2,
  381. startDate: this.startDate,
  382. endDate: this.endDate,
  383. sort: 0
  384. }
  385. ]
  386. };
  387. }
  388. return {
  389. saleDateType: 2,
  390. saleTimes: []
  391. };
  392. },
  393. buildComboDetails(comboItems) {
  394. const details = [];
  395. (comboItems || []).forEach((cat) => {
  396. const category = COMBO_CAT_TO_API[cat.id] || '5';
  397. (cat.items || []).forEach((item) => {
  398. if (!item.name) return;
  399. details.push({
  400. category,
  401. name: item.name,
  402. price: item.price !== '' && item.price != null ? Number(item.price) : 0,
  403. description: item.description || ''
  404. });
  405. });
  406. });
  407. return details;
  408. },
  409. buildSpecsAndSkus(draft) {
  410. const specs = [];
  411. (draft.specs || []).forEach((spec, si) => {
  412. (spec.values || []).forEach((v, oi) => {
  413. const row = {
  414. specName: spec.name,
  415. optionName: v.name,
  416. specSort: si,
  417. optionSort: oi,
  418. isSelected: v.selected ? 1 : 0
  419. };
  420. if (v.persistId) row.id = v.persistId;
  421. specs.push(row);
  422. });
  423. });
  424. const skus = (draft.combos || []).map((c, i) => ({
  425. specOptionNames: (c.label || '').replace(/\s*\|\s*/g, '/'),
  426. price: Number(c.price || 0),
  427. sellingPrice: Number(c.sellingPrice != null ? c.sellingPrice : c.originPrice || 0),
  428. stock: Number(c.stock || 0),
  429. costPrice: c.costPrice !== '' && c.costPrice != null ? Number(c.costPrice) : undefined,
  430. status: 1,
  431. sort: i
  432. }));
  433. return { specs, skus };
  434. },
  435. buildSubmitPayload(draft) {
  436. const isGroup = draft.belong === 'group';
  437. const isMulti = draft.specMode === 'multi';
  438. const sale = this.buildSaleTimes();
  439. const payload = {
  440. merchantId: getMerchantId(),
  441. category: draft.mealCategory || PRODUCT_TYPE_TO_CATEGORY[draft.productType] || '1',
  442. name: draft.name,
  443. productImage: draft.coverImages || draft.cover || '',
  444. detailImages: draft.detailImages || draft.detail || '',
  445. specialCategoryId: draft.category,
  446. productBelonging: belongingToApi(draft.belong),
  447. isGroupMeal: isGroup ? 1 : 0,
  448. salesSpecType: isMulti ? 2 : 1,
  449. isPackage: Number(draft.isCombo) === 1 ? 1 : 0,
  450. saleDateType: sale.saleDateType,
  451. saleTimes: sale.saleTimes
  452. };
  453. if (draft.isEdit && draft.editId) {
  454. payload.id = draft.editId;
  455. }
  456. if (!isMulti) {
  457. payload.price = Number(draft.price || 0);
  458. payload.sellingPrice = Number(draft.sellingPrice || 0);
  459. payload.dailyStock = Number(draft.stock || 0);
  460. if (draft.costPrice !== '' && draft.costPrice != null) {
  461. payload.costPrice = Number(draft.costPrice);
  462. }
  463. } else {
  464. const { specs, skus } = this.buildSpecsAndSkus(draft);
  465. payload.specs = specs;
  466. payload.skus = skus;
  467. const first = (draft.combos || [])[0] || {};
  468. payload.price = Number(first.price || 0);
  469. payload.sellingPrice = Number(
  470. first.sellingPrice != null ? first.sellingPrice : first.originPrice || 0
  471. );
  472. payload.dailyStock = Number(first.stock || 0);
  473. }
  474. if (Number(draft.isCombo) === 1) {
  475. payload.gmMerchantPackageDetails = this.buildComboDetails(draft.comboItems);
  476. }
  477. return payload;
  478. },
  479. onSubmit() {
  480. if (!this.canSubmit || this.submitting) {
  481. if (!this.canSubmit) uni.showToast({ title: '请完善售卖时间', icon: 'none' });
  482. return;
  483. }
  484. const draft = session.getDishDraft() || {};
  485. if (!draft.name) {
  486. uni.showToast({ title: '表单已失效,请重新填写', icon: 'none' });
  487. return;
  488. }
  489. const payload = this.buildSubmitPayload(draft);
  490. this.submitting = true;
  491. uni.showLoading({ title: '提交中' });
  492. merchantDishApi
  493. .packageSubmit(payload)
  494. .then((res) => {
  495. const ok = res.data.code === 200;
  496. this.$tip[ok ? 'success' : 'error'](res.data.message || (ok ? '送审成功!' : '提交失败'));
  497. if (ok) {
  498. session.clearDishDraft();
  499. setTimeout(() => {
  500. uni.redirectTo({ url: '/pages/merchantDish/dish/list' });
  501. }, 500);
  502. }
  503. })
  504. .finally(() => {
  505. this.submitting = false;
  506. uni.hideLoading();
  507. });
  508. }
  509. }
  510. };
  511. </script>
  512. <style lang="scss" scoped>
  513. .page {
  514. min-height: 100vh;
  515. background: #f7f8fc;
  516. padding-bottom: 160rpx;
  517. }
  518. .card {
  519. background: #fff;
  520. margin: 20rpx 24rpx;
  521. border-radius: 16rpx;
  522. padding: 24rpx;
  523. &.tip {
  524. font-size: 24rpx;
  525. color: #666;
  526. line-height: 1.6;
  527. }
  528. .label {
  529. font-size: 28rpx;
  530. color: #333;
  531. margin-bottom: 20rpx;
  532. display: block;
  533. &.required::before {
  534. content: '*';
  535. color: #ff4d4f;
  536. margin-right: 4rpx;
  537. }
  538. }
  539. }
  540. .cal-head {
  541. display: flex;
  542. justify-content: space-between;
  543. align-items: center;
  544. margin-bottom: 24rpx;
  545. }
  546. .cal-month {
  547. display: flex;
  548. align-items: center;
  549. gap: 8rpx;
  550. font-size: 30rpx;
  551. font-weight: 600;
  552. color: #222;
  553. }
  554. .cal-all {
  555. display: flex;
  556. align-items: center;
  557. gap: 12rpx;
  558. font-size: 26rpx;
  559. color: #666;
  560. .check {
  561. width: 32rpx;
  562. height: 32rpx;
  563. border-radius: 6rpx;
  564. border: 2rpx solid #c7c6ca;
  565. box-sizing: border-box;
  566. display: flex;
  567. align-items: center;
  568. justify-content: center;
  569. &.on {
  570. background: #449aff;
  571. border-color: #449aff;
  572. }
  573. }
  574. }
  575. .week {
  576. display: flex;
  577. margin-bottom: 8rpx;
  578. text {
  579. width: 14.28%;
  580. text-align: center;
  581. font-size: 24rpx;
  582. color: #999;
  583. }
  584. }
  585. .days {
  586. display: flex;
  587. flex-wrap: wrap;
  588. .day {
  589. width: 14.28%;
  590. height: 88rpx;
  591. display: flex;
  592. align-items: center;
  593. justify-content: center;
  594. .n {
  595. width: 72rpx;
  596. height: 72rpx;
  597. line-height: 70rpx;
  598. text-align: center;
  599. border-radius: 12rpx;
  600. font-size: 28rpx;
  601. color: #333;
  602. border: 2rpx solid transparent;
  603. box-sizing: border-box;
  604. }
  605. &.on .n {
  606. background: rgba(68, 154, 255, 0.12);
  607. border-color: #449aff;
  608. color: #449aff;
  609. }
  610. &.empty {
  611. pointer-events: none;
  612. }
  613. }
  614. }
  615. .range-row {
  616. display: flex;
  617. align-items: center;
  618. gap: 16rpx;
  619. }
  620. .range-box {
  621. flex: 1;
  622. height: 80rpx;
  623. line-height: 80rpx;
  624. text-align: center;
  625. font-size: 28rpx;
  626. color: #333;
  627. background: #f7f8fc;
  628. border-radius: 12rpx;
  629. border: 1rpx solid #e8e8e8;
  630. }
  631. .range-sep {
  632. color: #999;
  633. font-size: 28rpx;
  634. }
  635. .footer {
  636. position: fixed;
  637. left: 0;
  638. right: 0;
  639. bottom: 0;
  640. display: flex;
  641. gap: 24rpx;
  642. padding: 20rpx 40rpx calc(20rpx + env(safe-area-inset-bottom));
  643. background: #fff;
  644. }
  645. .f-btn {
  646. height: 88rpx;
  647. line-height: 88rpx;
  648. text-align: center;
  649. font-size: 30rpx;
  650. border-radius: 44rpx;
  651. &.outline {
  652. width: 240rpx;
  653. flex-shrink: 0;
  654. color: #449aff;
  655. border: 2rpx solid #449aff;
  656. box-sizing: border-box;
  657. line-height: 84rpx;
  658. background: #fff;
  659. }
  660. &.primary {
  661. flex: 1;
  662. color: #fff;
  663. background: linear-gradient( 89deg, #10ABFE 0%, #2260F6 100%);
  664. }
  665. &.disabled {
  666. opacity: 0.5;
  667. }
  668. }
  669. .pick-mask {
  670. position: fixed;
  671. left: 0;
  672. right: 0;
  673. top: 0;
  674. bottom: 0;
  675. background: rgba(0, 0, 0, 0.45);
  676. z-index: 1000;
  677. display: flex;
  678. align-items: flex-end;
  679. }
  680. .pick-sheet {
  681. width: 100%;
  682. background: #fff;
  683. border-radius: 24rpx 24rpx 0 0;
  684. padding: 0 32rpx calc(24rpx + env(safe-area-inset-bottom));
  685. box-sizing: border-box;
  686. }
  687. .pick-title {
  688. text-align: center;
  689. font-size: 32rpx;
  690. font-weight: 600;
  691. color: #222;
  692. padding: 36rpx 0 24rpx;
  693. }
  694. .range-preview {
  695. display: flex;
  696. gap: 20rpx;
  697. margin-bottom: 12rpx;
  698. }
  699. .preview-box {
  700. flex: 1;
  701. height: 72rpx;
  702. line-height: 70rpx;
  703. text-align: center;
  704. font-size: 26rpx;
  705. color: #666;
  706. background: #fff;
  707. border: 2rpx solid #e5e5e5;
  708. border-radius: 12rpx;
  709. box-sizing: border-box;
  710. &.on {
  711. color: #449aff;
  712. border-color: #449aff;
  713. background: rgba(68, 154, 255, 0.06);
  714. }
  715. }
  716. .pick-view {
  717. height: 400rpx;
  718. width: 100%;
  719. }
  720. .pick-item {
  721. height: 80rpx;
  722. line-height: 80rpx;
  723. text-align: center;
  724. font-size: 30rpx;
  725. color: #333;
  726. }
  727. .pick-confirm {
  728. margin-top: 12rpx;
  729. height: 88rpx;
  730. line-height: 88rpx;
  731. text-align: center;
  732. font-size: 30rpx;
  733. color: #fff;
  734. border-radius: 44rpx;
  735. background: linear-gradient( 89deg, #10ABFE 0%, #2260F6 100%);
  736. }
  737. ::v-deep .u-radio {
  738. margin-right: 24rpx;
  739. margin-bottom: 12rpx;
  740. }
  741. </style>