quotation-detail-content.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. <script setup>
  2. import { computed, ref, watch } from 'vue';
  3. import { request, parseResponseJson } from '@/utils/request';
  4. import { DEFAULT_PLACEHOLDER_IMAGE_URL } from '@/utils/staticAsset';
  5. import ClientSkeleton from '@/components/client/client-skeleton.vue';
  6. import ClientImageLoader from '@/components/client/client-image-loader.vue';
  7. const props = defineProps({
  8. quotationId: { type: String, default: '' }
  9. });
  10. const inquiryId = ref('');
  11. const status = ref('');
  12. const submitTime = ref('');
  13. const loading = ref(false);
  14. const keyword = ref('');
  15. const quickPriceFilter = ref('all');
  16. const selectedFirstCategoryId = ref('all');
  17. const selectedLeftFilterId = ref('all');
  18. const items = ref([]);
  19. const statusText = {
  20. submitted: '已提交',
  21. selected: '已采纳'
  22. };
  23. function getItemSecondCategoryId(item) {
  24. const fid = item.firstCategoryId || item.categoryId || '';
  25. if (item.secondCategoryId)
  26. return item.secondCategoryId;
  27. if (item.categoryId && item.categoryId !== fid)
  28. return item.categoryId;
  29. return '';
  30. }
  31. const firstCategoryCards = computed(() => {
  32. const map = new Map();
  33. items.value.forEach((item) => {
  34. const firstId = item.firstCategoryId || item.categoryId;
  35. if (!firstId)
  36. return;
  37. if (!map.has(firstId)) {
  38. map.set(firstId, {
  39. id: firstId,
  40. name: item.firstCategoryName || item.categoryName || '未分类',
  41. imageUrl: item.firstCategoryImage || DEFAULT_PLACEHOLDER_IMAGE_URL
  42. });
  43. }
  44. });
  45. return [{ id: 'all', name: '全部', imageUrl: DEFAULT_PLACEHOLDER_IMAGE_URL }, ...Array.from(map.values())];
  46. });
  47. const firstCategoryPages = computed(() => {
  48. const pages = [];
  49. const pageSize = 10;
  50. for (let i = 0; i < firstCategoryCards.value.length; i += pageSize) {
  51. pages.push(firstCategoryCards.value.slice(i, i + pageSize));
  52. }
  53. return pages;
  54. });
  55. const firstCategorySwiperIndex = ref(0);
  56. const leftCategoryOptions = computed(() => {
  57. if (selectedFirstCategoryId.value === 'all') {
  58. return firstCategoryCards.value;
  59. }
  60. const firstId = selectedFirstCategoryId.value;
  61. const firstItem = items.value.find((item) => (item.firstCategoryId || item.categoryId) === firstId);
  62. const firstName = firstItem?.firstCategoryName || firstItem?.categoryName || '未分类';
  63. const map = new Map();
  64. items.value.forEach((item) => {
  65. const fid = item.firstCategoryId || item.categoryId;
  66. if (fid !== firstId)
  67. return;
  68. const secondId = getItemSecondCategoryId(item);
  69. if (secondId) {
  70. map.set(secondId, item.secondCategoryName || item.categoryName || '未分类');
  71. }
  72. });
  73. return [
  74. { id: 'all', name: firstName },
  75. ...Array.from(map.entries()).map(([id, name]) => ({ id, name }))
  76. ];
  77. });
  78. const filteredItems = computed(() => {
  79. let list = items.value;
  80. if (selectedFirstCategoryId.value !== 'all') {
  81. list = list.filter((item) => (item.firstCategoryId || item.categoryId) === selectedFirstCategoryId.value);
  82. }
  83. if (selectedLeftFilterId.value !== 'all') {
  84. if (selectedFirstCategoryId.value === 'all') {
  85. list = list.filter((item) => (item.firstCategoryId || item.categoryId) === selectedLeftFilterId.value);
  86. }
  87. else {
  88. list = list.filter((item) => getItemSecondCategoryId(item) === selectedLeftFilterId.value);
  89. }
  90. }
  91. const kw = keyword.value.trim().toLowerCase();
  92. if (kw) {
  93. list = list.filter((item) => (item.productName || '').toLowerCase().includes(kw));
  94. }
  95. if (quickPriceFilter.value === 'empty') {
  96. list = list.filter((item) => !(Number(item.price) > 0));
  97. }
  98. else if (quickPriceFilter.value === 'filled') {
  99. list = list.filter((item) => Number(item.price) > 0);
  100. }
  101. return list;
  102. });
  103. function formatMeasureText(item) {
  104. const unitText = (item.unitName || '').trim();
  105. if (item.measureValue == null || item.measureValue === '') {
  106. return unitText ? `- ${unitText}` : '-';
  107. }
  108. const measureNum = Number(item.measureValue);
  109. if (!Number.isNaN(measureNum)) {
  110. const measureText = String(measureNum).replace(/\.?0+$/, '');
  111. return unitText ? `${measureText} ${unitText}` : measureText;
  112. }
  113. const measureText = String(item.measureValue).trim();
  114. return unitText ? `${measureText} ${unitText}` : measureText || '-';
  115. }
  116. function handleFirstCategoryClick(categoryId) {
  117. selectedFirstCategoryId.value = categoryId;
  118. selectedLeftFilterId.value = 'all';
  119. }
  120. function onFirstCategorySwiperChange(e) {
  121. const cur = e?.detail?.current;
  122. if (typeof cur === 'number')
  123. firstCategorySwiperIndex.value = cur;
  124. }
  125. function goFirstCategorySwiperPage(index) {
  126. firstCategorySwiperIndex.value = index;
  127. }
  128. watch(() => [selectedFirstCategoryId.value, leftCategoryOptions.value], ([, options]) => {
  129. const ids = new Set(options.map((o) => o.id));
  130. if (selectedLeftFilterId.value !== 'all' && !ids.has(selectedLeftFilterId.value)) {
  131. selectedLeftFilterId.value = 'all';
  132. }
  133. });
  134. watch(() => firstCategoryPages.value.length, () => {
  135. firstCategorySwiperIndex.value = 0;
  136. });
  137. function onSearchClick() {
  138. uni.hideKeyboard && uni.hideKeyboard();
  139. }
  140. function goProductDetail(item) {
  141. const qid = props.quotationId;
  142. if (!item.productId || !qid)
  143. return;
  144. uni.navigateTo({
  145. url: `/pages/supplier/product/detail?productId=${item.productId}&quotationId=${qid}&readonly=1`
  146. });
  147. }
  148. async function fetchData() {
  149. const qid = props.quotationId;
  150. if (!qid) {
  151. items.value = [];
  152. return;
  153. }
  154. loading.value = true;
  155. try {
  156. const quotationRes = await request({
  157. url: `/mini/supplier/quotations/${qid}`,
  158. method: 'GET'
  159. });
  160. const quotationBody = parseResponseJson(quotationRes);
  161. if (quotationBody.code !== 200 || !quotationBody.data) {
  162. uni.showToast({ title: '报价单加载失败', icon: 'none' });
  163. return;
  164. }
  165. const d = quotationBody.data;
  166. inquiryId.value = d.inquiryId || '';
  167. status.value = d.status || '';
  168. submitTime.value = d.submitTime || '';
  169. const quotationItems = d.items || [];
  170. const priceMap = quotationItems.reduce((acc, row) => {
  171. acc[row.productId] = Number(row.price || 0);
  172. return acc;
  173. }, {});
  174. if (!inquiryId.value) {
  175. items.value = quotationItems.map((row) => ({
  176. productId: row.productId,
  177. productName: row.productName,
  178. price: Number(row.price || 0)
  179. }));
  180. selectedFirstCategoryId.value = 'all';
  181. selectedLeftFilterId.value = 'all';
  182. return;
  183. }
  184. const inquiryRes = await request({
  185. url: `/mini/supplier/inquiries/${inquiryId.value}`,
  186. method: 'GET'
  187. });
  188. const inquiryBody = parseResponseJson(inquiryRes);
  189. const inquiryItems = inquiryBody.code === 200 ? (inquiryBody.data && inquiryBody.data.items) || [] : [];
  190. items.value = inquiryItems
  191. .filter((row) => row.productId in priceMap)
  192. .map((row) => ({
  193. ...row,
  194. price: Number(priceMap[row.productId] || 0)
  195. }));
  196. selectedFirstCategoryId.value = 'all';
  197. selectedLeftFilterId.value = 'all';
  198. }
  199. finally {
  200. loading.value = false;
  201. }
  202. }
  203. watch(() => props.quotationId, (id) => {
  204. if (id) {
  205. void fetchData();
  206. }
  207. else {
  208. items.value = [];
  209. inquiryId.value = '';
  210. status.value = '';
  211. submitTime.value = '';
  212. }
  213. }, { immediate: true });
  214. </script>
  215. <template>
  216. <view class="inquiry-page">
  217. <view v-if="loading" class="state">
  218. <ClientSkeleton variant="detail" :rows="2" />
  219. </view>
  220. <view v-else class="content-wrap">
  221. <view class="filter-top-wrap">
  222. <view class="top-row">
  223. <text class="top-row-left">报价日期:{{ submitTime || '-' }}</text>
  224. <text class="meta-status" :class="status === 'selected' ? 'selected' : ''">
  225. {{ statusText[status] || status || '-' }}
  226. </text>
  227. </view>
  228. <view class="search-wrap">
  229. <view class="search-bar">
  230. <input
  231. v-model="keyword"
  232. class="search-input"
  233. type="text"
  234. placeholder="搜索商品名称"
  235. placeholder-class="search-placeholder"
  236. confirm-type="search"
  237. @confirm="onSearchClick"
  238. />
  239. <text class="search-action" @click="onSearchClick">搜索</text>
  240. </view>
  241. </view>
  242. </view>
  243. <view class="category-section">
  244. <view class="section-head">
  245. <text class="section-title">商品分类</text>
  246. </view>
  247. <view class="first-category-wrap">
  248. <swiper
  249. v-if="firstCategoryPages.length > 1"
  250. class="first-category-swiper"
  251. :current="firstCategorySwiperIndex"
  252. :circular="false"
  253. :indicator-dots="false"
  254. @change="onFirstCategorySwiperChange"
  255. >
  256. <swiper-item v-for="(page, pageIdx) in firstCategoryPages" :key="`category-page-${pageIdx}`">
  257. <view class="first-category-grid">
  258. <view
  259. v-for="cat in page"
  260. :key="cat.id"
  261. class="first-category-card"
  262. :class="{ active: selectedFirstCategoryId === cat.id }"
  263. hover-class="first-category-card--hover"
  264. :hover-stay-time="120"
  265. @click="handleFirstCategoryClick(cat.id)"
  266. >
  267. <ClientImageLoader class="first-category-image" :src="cat.imageUrl || DEFAULT_PLACEHOLDER_IMAGE_URL" mode="aspectFill" radius="24rpx" />
  268. <text class="first-category-name">{{ cat.name }}</text>
  269. </view>
  270. </view>
  271. </swiper-item>
  272. </swiper>
  273. <view v-else class="first-category-grid">
  274. <view
  275. v-for="cat in firstCategoryCards"
  276. :key="cat.id"
  277. class="first-category-card"
  278. :class="{ active: selectedFirstCategoryId === cat.id }"
  279. hover-class="first-category-card--hover"
  280. :hover-stay-time="120"
  281. @click="handleFirstCategoryClick(cat.id)"
  282. >
  283. <ClientImageLoader class="first-category-image" :src="cat.imageUrl || DEFAULT_PLACEHOLDER_IMAGE_URL" mode="aspectFill" radius="24rpx" />
  284. <text class="first-category-name">{{ cat.name }}</text>
  285. </view>
  286. </view>
  287. </view>
  288. <view v-if="firstCategoryPages.length > 0" class="first-level-indicator">
  289. <view
  290. v-for="(_, idx) in firstCategoryPages"
  291. :key="`dot-${idx}`"
  292. class="indicator-dot"
  293. :class="{ active: firstCategorySwiperIndex === idx }"
  294. @click="goFirstCategorySwiperPage(idx)"
  295. />
  296. </view>
  297. </view>
  298. <view class="product-layout">
  299. <view class="product-body">
  300. <scroll-view scroll-y class="category-pane" :show-scrollbar="true">
  301. <view
  302. v-for="(cat, cIdx) in leftCategoryOptions"
  303. :key="`${selectedFirstCategoryId}-${cIdx}-${cat.id}`"
  304. class="category-item"
  305. :class="{ active: selectedLeftFilterId === cat.id }"
  306. @click="selectedLeftFilterId = cat.id"
  307. >
  308. {{ cat.name }}
  309. </view>
  310. </scroll-view>
  311. <view class="goods-pane">
  312. <view class="quick-tags">
  313. <view
  314. class="tag"
  315. :class="{ active: quickPriceFilter === 'all' }"
  316. @click="quickPriceFilter = 'all'"
  317. >
  318. 全部
  319. </view>
  320. <view
  321. class="tag"
  322. :class="{ active: quickPriceFilter === 'empty' }"
  323. @click="quickPriceFilter = 'empty'"
  324. >
  325. 价格为空
  326. </view>
  327. <view
  328. class="tag"
  329. :class="{ active: quickPriceFilter === 'filled' }"
  330. @click="quickPriceFilter = 'filled'"
  331. >
  332. 价格不为空
  333. </view>
  334. </view>
  335. <scroll-view scroll-y class="goods-scroll" :show-scrollbar="true">
  336. <view class="goods-scroll-inner">
  337. <view
  338. v-for="item in filteredItems"
  339. :key="item.productId"
  340. class="goods-card"
  341. hover-class="goods-card--pressed"
  342. :hover-stay-time="120"
  343. @click="goProductDetail(item)"
  344. >
  345. <ClientImageLoader class="goods-image" :src="item.imageUrl || DEFAULT_PLACEHOLDER_IMAGE_URL" mode="aspectFill" radius="16rpx" />
  346. <view class="goods-main">
  347. <text class="goods-name">{{ item.productName || '-' }}</text>
  348. <text class="goods-spec">{{ formatMeasureText(item) }}</text>
  349. <view class="price-row">
  350. <text class="price-label">报价</text>
  351. <text class="price-value">{{ Number(item.price || 0).toFixed(2) }}</text>
  352. <text class="price-unit">元</text>
  353. </view>
  354. </view>
  355. </view>
  356. <view v-if="filteredItems.length === 0" class="empty-list-tip">暂无匹配商品</view>
  357. <view class="goods-scroll-spacer" />
  358. </view>
  359. </scroll-view>
  360. </view>
  361. </view>
  362. </view>
  363. </view>
  364. </view>
  365. </template>
  366. <style lang="scss">
  367. .search-placeholder {
  368. color: #8aa39a;
  369. }
  370. </style>
  371. <style lang="scss" scoped>
  372. .inquiry-page {
  373. height: 100%;
  374. max-height: 100%;
  375. min-height: 0;
  376. background: #f5f8f7;
  377. overflow: hidden;
  378. display: flex;
  379. flex-direction: column;
  380. box-sizing: border-box;
  381. }
  382. .state {
  383. flex: 1;
  384. min-height: 0;
  385. padding: 0;
  386. }
  387. .content-wrap {
  388. flex: 1;
  389. min-height: 0;
  390. padding: 0 32rpx;
  391. display: flex;
  392. flex-direction: column;
  393. box-sizing: border-box;
  394. }
  395. .filter-top-wrap {
  396. flex-shrink: 0;
  397. background: linear-gradient(145deg, #0a9f6d, #07c160);
  398. border-bottom-left-radius: 28rpx;
  399. border-bottom-right-radius: 28rpx;
  400. padding: 20rpx 24rpx 24rpx;
  401. margin: 0 -32rpx 0;
  402. }
  403. .top-row {
  404. display: flex;
  405. align-items: center;
  406. justify-content: space-between;
  407. margin-bottom: 12rpx;
  408. gap: 12rpx;
  409. }
  410. .top-row-left {
  411. color: #ffffff;
  412. font-size: 28rpx;
  413. font-weight: 600;
  414. min-width: 0;
  415. }
  416. .meta-status {
  417. padding: 6rpx 16rpx;
  418. border-radius: 999rpx;
  419. font-size: 22rpx;
  420. color: #087f57;
  421. background: #d1fae5;
  422. font-weight: 600;
  423. }
  424. .meta-status.selected {
  425. color: #166534;
  426. background: #bbf7d0;
  427. }
  428. .search-wrap {
  429. margin-top: 8rpx;
  430. }
  431. .search-bar {
  432. display: flex;
  433. align-items: center;
  434. height: 72rpx;
  435. background: #ffffff;
  436. border: 1rpx solid #d9e9e1;
  437. border-radius: 36rpx;
  438. padding: 0 8rpx 0 24rpx;
  439. box-sizing: border-box;
  440. }
  441. .search-input {
  442. flex: 1;
  443. min-width: 0;
  444. height: 72rpx;
  445. border: none;
  446. padding: 0;
  447. font-size: 28rpx;
  448. color: #303133;
  449. background: transparent;
  450. box-sizing: border-box;
  451. }
  452. .search-action {
  453. flex-shrink: 0;
  454. padding: 0 16rpx 0 8rpx;
  455. font-size: 24rpx;
  456. color: #0a9f6d;
  457. font-weight: 600;
  458. }
  459. .category-section {
  460. flex-shrink: 0;
  461. margin-top: 24rpx;
  462. }
  463. .section-head {
  464. display: flex;
  465. align-items: center;
  466. justify-content: space-between;
  467. margin-bottom: 16rpx;
  468. }
  469. .section-title {
  470. font-size: 32rpx;
  471. font-weight: 600;
  472. color: #1f2a26;
  473. }
  474. .product-layout {
  475. flex: 1;
  476. min-height: 0;
  477. margin-top: 20rpx;
  478. margin-left: -32rpx;
  479. margin-right: -32rpx;
  480. }
  481. .first-category-wrap {
  482. background: #ffffff;
  483. border: 1rpx solid #d9ebe3;
  484. border-radius: 28rpx;
  485. padding: 8rpx;
  486. box-sizing: border-box;
  487. overflow: hidden;
  488. }
  489. .first-category-swiper {
  490. height: 336rpx;
  491. }
  492. .first-category-grid {
  493. display: grid;
  494. grid-template-columns: repeat(5, 1fr);
  495. grid-template-rows: repeat(2, minmax(0, 1fr));
  496. gap: 16rpx;
  497. padding: 8rpx;
  498. box-sizing: border-box;
  499. width: 100%;
  500. height: 100%;
  501. align-content: start;
  502. }
  503. .first-category-card {
  504. display: flex;
  505. flex-direction: column;
  506. align-items: center;
  507. justify-content: flex-start;
  508. min-height: 152rpx;
  509. padding: 8rpx 4rpx 8rpx;
  510. border-radius: 28rpx;
  511. background: transparent;
  512. border: none;
  513. box-sizing: border-box;
  514. transition: opacity 0.15s ease, transform 0.15s ease;
  515. &.active {
  516. background: linear-gradient(145deg, #e7f8f0, #f1fbf6);
  517. color: #0a9f6d;
  518. font-weight: 600;
  519. box-shadow: 0 4rpx 8rpx rgba(10, 159, 109, 0.12);
  520. }
  521. }
  522. .first-category-card--hover {
  523. opacity: 0.88;
  524. }
  525. .first-category-image {
  526. width: 84rpx;
  527. height: 84rpx;
  528. border-radius: 24rpx;
  529. background: linear-gradient(145deg, #dff1e8, #cdebdc);
  530. flex-shrink: 0;
  531. }
  532. .first-category-card.active .first-category-image {
  533. box-shadow: 0 4rpx 14rpx rgba(10, 159, 109, 0.35);
  534. }
  535. .first-category-name {
  536. margin-top: 8rpx;
  537. width: 100%;
  538. font-size: 22rpx;
  539. line-height: 1.2;
  540. color: #4f665d;
  541. text-align: center;
  542. overflow: hidden;
  543. text-overflow: ellipsis;
  544. white-space: nowrap;
  545. }
  546. .first-category-card.active .first-category-name {
  547. color: #0a9f6d;
  548. }
  549. .first-level-indicator {
  550. display: flex;
  551. justify-content: center;
  552. align-items: center;
  553. gap: 16rpx;
  554. margin: 16rpx auto 0;
  555. }
  556. .indicator-dot {
  557. width: 36rpx;
  558. height: 8rpx;
  559. border-radius: 999rpx;
  560. background: #dbe9e2;
  561. transition: width 0.2s ease, background 0.2s ease;
  562. }
  563. .indicator-dot.active {
  564. width: 56rpx;
  565. background: linear-gradient(145deg, #14b883, #0a9f6d);
  566. }
  567. .product-body {
  568. display: flex;
  569. height: 100%;
  570. min-height: 0;
  571. min-height: 880rpx;
  572. border: 1rpx solid #d9ebe3;
  573. border-radius: 24rpx;
  574. overflow: hidden;
  575. background: #ffffff;
  576. }
  577. .category-pane {
  578. width: 184rpx;
  579. height: 100%;
  580. background: #f8fcfa;
  581. border-radius: 24rpx 0 0 24rpx;
  582. margin-right: 0;
  583. border-right: 1rpx solid #edf5f1;
  584. }
  585. .category-item {
  586. min-height: 84rpx;
  587. display: flex;
  588. align-items: center;
  589. justify-content: center;
  590. padding: 0 16rpx;
  591. font-size: 24rpx;
  592. color: #5f726a;
  593. text-align: center;
  594. border-left: 6rpx solid transparent;
  595. background: transparent;
  596. &.active {
  597. color: #0a9f6d;
  598. background: #ffffff;
  599. border-left-color: #0a9f6d;
  600. font-weight: 600;
  601. }
  602. }
  603. .goods-pane {
  604. flex: 1;
  605. min-width: 0;
  606. min-height: 0;
  607. height: 100%;
  608. background: #ffffff;
  609. border: 1rpx solid #d9ebe3;
  610. border-left: none;
  611. padding: 16rpx 0 0;
  612. display: flex;
  613. flex-direction: column;
  614. }
  615. .quick-tags {
  616. margin-bottom: 12rpx;
  617. padding: 0 20rpx;
  618. box-sizing: border-box;
  619. background: transparent;
  620. display: flex;
  621. gap: 12rpx;
  622. justify-content: flex-start;
  623. flex-wrap: wrap;
  624. }
  625. .tag {
  626. padding: 6rpx 14rpx;
  627. border-radius: 999rpx;
  628. font-size: 22rpx;
  629. color: #303133;
  630. background: #f2f3f5;
  631. border: 1rpx solid #f2f3f5;
  632. &.active {
  633. color: #ffffff;
  634. background: #0a9f6d;
  635. border-color: #0a9f6d;
  636. font-weight: 600;
  637. }
  638. }
  639. .goods-scroll {
  640. flex: 1;
  641. min-height: 0;
  642. height: 100%;
  643. }
  644. .goods-scroll-inner {
  645. padding: 0 20rpx;
  646. box-sizing: border-box;
  647. }
  648. .goods-scroll-spacer {
  649. height: calc(160rpx + env(safe-area-inset-bottom));
  650. flex-shrink: 0;
  651. }
  652. .empty-list-tip {
  653. text-align: center;
  654. color: #909399;
  655. font-size: 28rpx;
  656. padding: 40rpx 0;
  657. }
  658. .goods-card {
  659. display: grid;
  660. grid-template-columns: minmax(112rpx, 36%) minmax(0, 1fr);
  661. gap: min(16rpx, 2.5vw);
  662. align-items: stretch;
  663. background: #fff;
  664. padding: 0;
  665. border: 1rpx solid #d9ebe3;
  666. border-radius: 18rpx;
  667. margin-bottom: 16rpx;
  668. overflow: hidden;
  669. box-sizing: border-box;
  670. height: 200rpx;
  671. }
  672. .goods-card--pressed {
  673. opacity: 0.96;
  674. }
  675. .goods-image {
  676. width: 100%;
  677. align-self: stretch;
  678. height: 100%;
  679. min-height: 140rpx;
  680. border-radius: 16rpx;
  681. background: linear-gradient(145deg, #f4fbf8, #e4f6ef);
  682. flex-shrink: 0;
  683. }
  684. .goods-main {
  685. padding: 10rpx 12rpx 10rpx 0;
  686. flex: 1;
  687. min-width: 0;
  688. min-height: 0;
  689. display: flex;
  690. flex-direction: column;
  691. justify-content: space-between;
  692. }
  693. .goods-name {
  694. display: -webkit-box;
  695. -webkit-box-orient: vertical;
  696. -webkit-line-clamp: 2;
  697. overflow: hidden;
  698. font-size: 26rpx;
  699. line-height: 1.35;
  700. color: #1f2a26;
  701. font-weight: 500;
  702. word-break: break-word;
  703. min-height: 70rpx;
  704. }
  705. .goods-spec {
  706. display: block;
  707. margin: 2rpx 0 8rpx;
  708. font-size: 22rpx;
  709. color: #909399;
  710. overflow: hidden;
  711. text-overflow: ellipsis;
  712. white-space: nowrap;
  713. }
  714. .price-row {
  715. display: flex;
  716. align-items: center;
  717. margin-top: auto;
  718. padding: 6rpx 10rpx;
  719. background: #f6fbf8;
  720. border-radius: 12rpx;
  721. }
  722. .price-label {
  723. font-size: 24rpx;
  724. color: #606266;
  725. margin-right: 12rpx;
  726. }
  727. .price-value {
  728. flex: 1;
  729. min-width: 0;
  730. overflow: hidden;
  731. text-overflow: ellipsis;
  732. white-space: nowrap;
  733. font-size: 24rpx;
  734. font-weight: 600;
  735. color: #303133;
  736. }
  737. .price-unit {
  738. margin-left: 12rpx;
  739. font-size: 24rpx;
  740. color: #606266;
  741. }
  742. </style>