shops.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <template>
  2. <div class="owner-shops">
  3. <van-nav-bar :title="$t('owner.shops.title')" left-arrow @click-left="goBack" fixed placeholder>
  4. <template #right>
  5. <van-icon name="add-o" size="20" @click="addShop" />
  6. </template>
  7. </van-nav-bar>
  8. <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
  9. <div class="shop-list">
  10. <div
  11. v-for="shop in shops"
  12. :key="shop.id"
  13. class="shop-card"
  14. :class="{ selected: shop.id === selectedShopId }"
  15. @click="selectShop(shop)"
  16. >
  17. <div class="shop-header">
  18. <div class="shop-logo">
  19. <van-image
  20. v-if="shop.logo"
  21. :src="shop.logo"
  22. width="50"
  23. height="50"
  24. round
  25. fit="cover"
  26. />
  27. <div v-else class="logo-placeholder">
  28. <van-icon name="shop-o" size="24" />
  29. </div>
  30. </div>
  31. <div class="shop-info">
  32. <div class="shop-name">
  33. {{ shop.name }}
  34. <van-tag :type="getStatusType(shop.status)">
  35. {{ getStatusText(shop.status) }}
  36. </van-tag>
  37. </div>
  38. <div class="shop-address">{{ shop.address }}</div>
  39. </div>
  40. <van-icon name="arrow" class="arrow" />
  41. </div>
  42. <div class="shop-actions">
  43. <van-button size="small" plain type="primary" @click.stop="enterShop(shop)">
  44. {{ $t('owner.shops.actions.enter') }}
  45. </van-button>
  46. <van-button size="small" plain @click.stop="editShop(shop)">
  47. {{ $t('common.edit') }}
  48. </van-button>
  49. <van-button
  50. size="small"
  51. plain
  52. :type="shop.status === 'operating' ? 'warning' : 'success'"
  53. @click.stop="toggleStatus(shop)"
  54. >
  55. {{ shop.status === 'operating' ? $t('owner.shops.actions.stopOperating') : $t('owner.shops.actions.startOperating') }}
  56. </van-button>
  57. </div>
  58. </div>
  59. <van-empty v-if="shops.length === 0" :description="$t('owner.shops.empty')" />
  60. </div>
  61. </van-pull-refresh>
  62. <!-- 当前选中店铺指示 -->
  63. <div v-if="selectedShop" class="current-shop-bar">
  64. <span>{{ $t('owner.shops.currentShopLabel') }} <strong>{{ selectedShop.name }}</strong></span>
  65. <van-button size="small" type="primary" @click="enterSelectedShop">
  66. {{ $t('owner.shops.actions.enterManagement') }}
  67. </van-button>
  68. </div>
  69. </div>
  70. </template>
  71. <script setup lang="ts">
  72. import { ref, computed, onMounted } from 'vue'
  73. import { useRouter } from 'vue-router'
  74. import { useI18n } from 'vue-i18n'
  75. import { showToast, showConfirmDialog } from 'vant'
  76. const { t } = useI18n()
  77. import { useCompanyStore, type Shop, type ShopStatus } from '@/store/modules/company'
  78. const router = useRouter()
  79. const companyStore = useCompanyStore()
  80. const refreshing = ref(false)
  81. const shops = computed(() => companyStore.shops)
  82. const selectedShopId = computed(() => companyStore.selectedShopId)
  83. const selectedShop = computed(() => companyStore.selectedShop)
  84. const getStatusType = (status: ShopStatus): 'success' | 'warning' | 'default' => {
  85. const types: Record<ShopStatus, 'success' | 'warning' | 'default'> = {
  86. operating: 'success',
  87. closed: 'warning',
  88. preparing: 'default'
  89. }
  90. return types[status]
  91. }
  92. const getStatusText = (status: ShopStatus) => {
  93. const texts: Record<ShopStatus, string> = {
  94. operating: t('owner.shops.status.operating'),
  95. closed: t('owner.shops.status.resting'),
  96. preparing: t('owner.shops.status.preparing')
  97. }
  98. return texts[status]
  99. }
  100. const goBack = () => {
  101. router.back()
  102. }
  103. const onRefresh = async () => {
  104. await companyStore.loadCompanyData()
  105. refreshing.value = false
  106. }
  107. const selectShop = (shop: Shop) => {
  108. companyStore.selectShop(shop.id)
  109. }
  110. const enterShop = (shop: Shop) => {
  111. companyStore.selectShop(shop.id)
  112. router.push(`/shop/${shop.id}/dashboard`)
  113. }
  114. const enterSelectedShop = () => {
  115. if (selectedShop.value) {
  116. router.push(`/shop/${selectedShop.value.id}/dashboard`)
  117. }
  118. }
  119. const editShop = (shop: Shop) => {
  120. showToast(t('owner.shops.messages.editShop', { name: shop.name }))
  121. }
  122. const addShop = () => {
  123. showToast(t('owner.shops.messages.addShop'))
  124. }
  125. const toggleStatus = async (shop: Shop) => {
  126. const actionText = shop.status === 'operating'
  127. ? t('owner.shops.actions.stopOperating')
  128. : t('owner.shops.actions.startOperating')
  129. try {
  130. await showConfirmDialog({
  131. title: t('common.confirmAction'),
  132. message: t('owner.shops.messages.confirmStatusChange', { action: actionText, name: shop.name })
  133. })
  134. // TODO: 调用API更新状态
  135. showToast(t('owner.shops.messages.statusChanged', { action: actionText }))
  136. } catch {
  137. // 用户取消
  138. }
  139. }
  140. onMounted(() => {
  141. if (shops.value.length === 0) {
  142. companyStore.loadCompanyData()
  143. }
  144. })
  145. </script>
  146. <style scoped>
  147. .owner-shops {
  148. min-height: 100vh;
  149. background: #f5f5f5;
  150. padding-bottom: 70px;
  151. }
  152. .shop-list {
  153. padding: 12px;
  154. }
  155. .shop-card {
  156. background: #fff;
  157. border-radius: 12px;
  158. padding: 16px;
  159. margin-bottom: 12px;
  160. border: 2px solid transparent;
  161. transition: border-color 0.2s;
  162. }
  163. .shop-card.selected {
  164. border-color: #1989fa;
  165. }
  166. .shop-header {
  167. display: flex;
  168. align-items: center;
  169. gap: 12px;
  170. }
  171. .logo-placeholder {
  172. width: 50px;
  173. height: 50px;
  174. background: #f5f5f5;
  175. border-radius: 50%;
  176. display: flex;
  177. align-items: center;
  178. justify-content: center;
  179. color: #999;
  180. }
  181. .shop-info {
  182. flex: 1;
  183. }
  184. .shop-name {
  185. font-size: 16px;
  186. font-weight: 500;
  187. display: flex;
  188. align-items: center;
  189. gap: 8px;
  190. }
  191. .shop-address {
  192. font-size: 13px;
  193. color: #999;
  194. margin-top: 4px;
  195. }
  196. .arrow {
  197. color: #ccc;
  198. }
  199. .shop-actions {
  200. display: flex;
  201. gap: 8px;
  202. margin-top: 12px;
  203. padding-top: 12px;
  204. border-top: 1px solid #f5f5f5;
  205. }
  206. .current-shop-bar {
  207. position: fixed;
  208. bottom: 0;
  209. left: 0;
  210. right: 0;
  211. background: #fff;
  212. padding: 12px 16px;
  213. display: flex;
  214. justify-content: space-between;
  215. align-items: center;
  216. box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
  217. }
  218. .current-shop-bar span {
  219. font-size: 14px;
  220. color: #666;
  221. }
  222. .current-shop-bar strong {
  223. color: #1989fa;
  224. }
  225. </style>