shareStore.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <view class="page">
  3. <u-navbar title="共享门店" :autoBack="true" :placeholder="true"></u-navbar>
  4. <view class="list" v-if="storeList.length">
  5. <view class="store-card" v-for="item in storeList" :key="item.id">
  6. <view class="del-btn" @click.stop="onDelete(item)">
  7. <image class="del-bg" src="/static/topUp/del.png" mode="scaleToFill"></image>
  8. <text class="del-text">删除</text>
  9. </view>
  10. <image class="logo" :src="item.logo || defaultLogo" mode="aspectFill"></image>
  11. <view class="info">
  12. <view class="name u-line-1">{{ item.name }}</view>
  13. <view class="meta">
  14. <view class="stars">
  15. <u-icon
  16. v-for="n in 5"
  17. :key="n"
  18. name="heart-fill"
  19. :color="n <= Math.round(item.rating) ? '#FF4D4F' : '#E5E5E5'"
  20. size="20"
  21. ></u-icon>
  22. </view>
  23. <text class="meta-text">{{ formatRating(item) }}</text>
  24. </view>
  25. <view class="addr u-line-1">{{ item.address || '暂无地址' }}</view>
  26. <view class="distance" v-if="item.distance">距您{{ item.distance }}</view>
  27. </view>
  28. </view>
  29. </view>
  30. <view class="empty-wrap" v-else-if="loading">
  31. <u-loading mode="circle" size="48"></u-loading>
  32. <text class="empty-text">加载中...</text>
  33. </view>
  34. <view class="empty-wrap" v-else>
  35. <u-empty text="暂无数据" mode="list"></u-empty>
  36. </view>
  37. <view class="footer">
  38. <view class="add-btn" :class="{ disabled: !canAdd }" @click="goAdd">添加</view>
  39. </view>
  40. <!-- 删除确认弹窗 -->
  41. <u-popup v-model="confirmVisible" mode="center" border-radius="24" :mask-close-able="false">
  42. <view class="confirm-dialog">
  43. <view class="confirm-title">提示</view>
  44. <view class="confirm-body">
  45. <text class="confirm-text">是否确认删除该共享门店?</text>
  46. </view>
  47. <view class="confirm-footer">
  48. <view class="btn cancel" @click="onConfirmCancel">取消</view>
  49. <view class="btn ok" @click="confirmDelete">确定</view>
  50. </view>
  51. </view>
  52. </u-popup>
  53. </view>
  54. </template>
  55. <script>
  56. import prepaidApi, { getMerchantId } from '@/api/memberPrepaid.js'
  57. const DEFAULT_LOGO = '/static/index/shop.png'
  58. export default {
  59. data() {
  60. return {
  61. defaultLogo: DEFAULT_LOGO,
  62. canAdd: false,
  63. loading: false,
  64. storeList: [],
  65. confirmVisible: false,
  66. pendingDelete: null,
  67. deleting: false
  68. };
  69. },
  70. onShow() {
  71. this.confirmVisible = false;
  72. this.pendingDelete = null;
  73. this.refreshEnabled();
  74. this.loadList();
  75. },
  76. methods: {
  77. formatRating(item) {
  78. const score = Number(item.rating) || 0;
  79. const count = item.reviewCount;
  80. let countText = '';
  81. if (count === undefined || count === null || count === '') {
  82. countText = '暂无评论';
  83. } else if (typeof count === 'string') {
  84. countText = /评价|评论/.test(count) ? count : `${count}条评论`;
  85. } else {
  86. countText = `${count}条评论`;
  87. }
  88. return `${score.toFixed(1)}分 | ${countText}`;
  89. },
  90. mapStore(item) {
  91. return {
  92. id: item.merchantId || item.id,
  93. name: item.storeName || item.name || '',
  94. logo: item.headPhoto || item.logo || DEFAULT_LOGO,
  95. address: item.address || '',
  96. rating: Number(item.grade || item.rating || 0),
  97. reviewCount: item.commentNum ?? item.reviewCount ?? '',
  98. distance: item.distance || item.distanceText || ''
  99. };
  100. },
  101. parseRecords(result) {
  102. if (!result) return [];
  103. if (Array.isArray(result)) return result;
  104. if (Array.isArray(result.records)) return result.records;
  105. if (result.page && Array.isArray(result.page.records)) return result.page.records;
  106. return [];
  107. },
  108. refreshEnabled() {
  109. const merchantId = getMerchantId();
  110. if (!merchantId) {
  111. this.canAdd = false;
  112. return;
  113. }
  114. prepaidApi.getFeatureStatus(merchantId).then((res) => {
  115. if (res.data.code !== 200) return;
  116. const data = res.data.result || {};
  117. this.canAdd = Number(data.auditStatus) === 2 && Number(data.operationStatus) === 1;
  118. });
  119. },
  120. loadList() {
  121. this.loading = true;
  122. prepaidApi
  123. .getSharedStores({ pageNo: 1, pageSize: 100 })
  124. .then((res) => {
  125. if (res.data.code !== 200) {
  126. this.storeList = [];
  127. uni.showToast({ title: res.data.message || '加载失败', icon: 'none' });
  128. return;
  129. }
  130. const records = this.parseRecords(res.data.result);
  131. this.storeList = records.map((item) => this.mapStore(item));
  132. })
  133. .catch(() => {
  134. this.storeList = [];
  135. uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
  136. })
  137. .then(() => {
  138. this.loading = false;
  139. });
  140. },
  141. goAdd() {
  142. if (!this.canAdd) {
  143. uni.showToast({ title: '请先开启储值功能', icon: 'none' });
  144. return;
  145. }
  146. uni.navigateTo({ url: '/pages/topUp/shareStoreAdd' });
  147. },
  148. onDelete(item) {
  149. this.pendingDelete = item;
  150. this.confirmVisible = true;
  151. },
  152. onConfirmCancel() {
  153. this.confirmVisible = false;
  154. this.pendingDelete = null;
  155. },
  156. confirmDelete() {
  157. const item = this.pendingDelete;
  158. this.confirmVisible = false;
  159. this.pendingDelete = null;
  160. if (!item || this.deleting) return;
  161. this.deleting = true;
  162. prepaidApi
  163. .removeSharedStore(item.id)
  164. .then((res) => {
  165. if (res.data.code === 200) {
  166. uni.showToast({ title: '已删除共享', icon: 'none' });
  167. this.loadList();
  168. } else {
  169. uni.showToast({ title: res.data.message || '删除失败', icon: 'none' });
  170. }
  171. })
  172. .catch(() => {
  173. uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
  174. })
  175. .then(() => {
  176. this.deleting = false;
  177. });
  178. }
  179. }
  180. };
  181. </script>
  182. <style scoped lang="scss">
  183. .page {
  184. min-height: 100vh;
  185. background: #f7f7f7;
  186. padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
  187. box-sizing: border-box;
  188. }
  189. .list {
  190. padding: 24rpx;
  191. }
  192. .store-card {
  193. position: relative;
  194. display: flex;
  195. align-items: flex-start;
  196. background: #fff;
  197. border-radius: 16rpx;
  198. padding: 24rpx;
  199. margin-bottom: 20rpx;
  200. overflow: hidden;
  201. .del-btn {
  202. position: absolute;
  203. top: 0;
  204. right: 0;
  205. width: 96rpx;
  206. height: 44rpx;
  207. z-index: 2;
  208. .del-bg {
  209. position: absolute;
  210. left: 0;
  211. top: 0;
  212. width: 100%;
  213. height: 100%;
  214. }
  215. .del-text {
  216. position: relative;
  217. z-index: 1;
  218. display: flex;
  219. align-items: center;
  220. justify-content: center;
  221. width: 100%;
  222. height: 100%;
  223. font-size: 22rpx;
  224. color: #1C7CF9;
  225. line-height: 1;
  226. padding-left: 8rpx;
  227. box-sizing: border-box;
  228. }
  229. }
  230. .logo {
  231. width: 120rpx;
  232. height: 120rpx;
  233. border-radius: 12rpx;
  234. background: #f0f0f0;
  235. flex-shrink: 0;
  236. margin-right: 20rpx;
  237. }
  238. .info {
  239. flex: 1;
  240. min-width: 0;
  241. padding-right: 80rpx;
  242. position: relative;
  243. .name {
  244. font-size: 30rpx;
  245. font-weight: 600;
  246. color: #333;
  247. line-height: 1.35;
  248. margin-bottom: 10rpx;
  249. }
  250. .meta {
  251. display: flex;
  252. align-items: center;
  253. gap: 8rpx;
  254. margin-bottom: 10rpx;
  255. .stars {
  256. display: flex;
  257. align-items: center;
  258. gap: 2rpx;
  259. }
  260. .meta-text {
  261. font-size: 22rpx;
  262. color: #FF4D4F;
  263. line-height: 1.2;
  264. }
  265. }
  266. .addr {
  267. font-size: 22rpx;
  268. color: #999;
  269. line-height: 1.4;
  270. padding-right: 120rpx;
  271. }
  272. .distance {
  273. position: absolute;
  274. right: 0;
  275. bottom: 0;
  276. font-size: 22rpx;
  277. color: #666;
  278. line-height: 1.4;
  279. }
  280. }
  281. }
  282. .empty-wrap {
  283. padding-top: 200rpx;
  284. display: flex;
  285. flex-direction: column;
  286. align-items: center;
  287. justify-content: center;
  288. gap: 16rpx;
  289. .empty-text {
  290. font-size: 26rpx;
  291. color: #999;
  292. }
  293. }
  294. .footer {
  295. position: fixed;
  296. left: 0;
  297. right: 0;
  298. bottom: 0;
  299. padding: 16rpx 32rpx calc(16rpx + env(safe-area-inset-bottom));
  300. background: #fff;
  301. box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
  302. z-index: 20;
  303. .add-btn {
  304. height: 88rpx;
  305. line-height: 88rpx;
  306. text-align: center;
  307. background: linear-gradient(269deg, #2260F6 0%, #0FB0FF 100%);
  308. border-radius: 160rpx;
  309. color: #fff;
  310. font-size: 32rpx;
  311. font-weight: 500;
  312. &.disabled {
  313. background: #B7D4FF;
  314. color: #fff;
  315. }
  316. }
  317. }
  318. .confirm-dialog {
  319. width: 560rpx;
  320. background: #fff;
  321. border-radius: 24rpx;
  322. overflow: hidden;
  323. padding: 48rpx 40rpx 40rpx;
  324. box-sizing: border-box;
  325. .confirm-title {
  326. text-align: center;
  327. font-size: 34rpx;
  328. font-weight: 600;
  329. color: #333;
  330. line-height: 1.4;
  331. margin-bottom: 28rpx;
  332. }
  333. .confirm-body {
  334. padding: 0 8rpx 40rpx;
  335. .confirm-text {
  336. display: block;
  337. text-align: center;
  338. font-size: 28rpx;
  339. color: #333;
  340. line-height: 1.6;
  341. }
  342. }
  343. .confirm-footer {
  344. display: flex;
  345. align-items: center;
  346. justify-content: space-between;
  347. gap: 24rpx;
  348. .btn {
  349. flex: 1;
  350. height: 80rpx;
  351. line-height: 80rpx;
  352. text-align: center;
  353. border-radius: 40rpx;
  354. font-size: 30rpx;
  355. box-sizing: border-box;
  356. }
  357. .cancel {
  358. color: #999;
  359. background: #fff;
  360. border: 2rpx solid #dcdcdc;
  361. }
  362. .ok {
  363. color: #fff;
  364. background: linear-gradient(90deg, #3ab3ff 0%, #2472ff 100%);
  365. border: none;
  366. }
  367. }
  368. }
  369. </style>