combo.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <template>
  2. <view class="page">
  3. <u-navbar title="新建套餐" :autoBack="true" :placeholder="true"></u-navbar>
  4. <view class="content">
  5. <u-collapse ref="collapse" :accordion="false" @change="onCollapse">
  6. <u-collapse-item
  7. v-for="(cat, ci) in categories"
  8. :key="cat.id"
  9. :name="cat.id"
  10. :open="activeNames.includes(cat.id)"
  11. >
  12. <view slot="title" class="c-title">
  13. {{ cat.name }}
  14. <text class="num" v-if="filledCount(cat)">({{ filledCount(cat) }})</text>
  15. </view>
  16. <view class="dish-item" v-for="(dish, di) in cat.items" :key="dish.id">
  17. <view class="dish-head">
  18. <u-icon name="edit-pen" color="#333" size="28"></u-icon>
  19. <text class="dish-title">{{ cat.name }}{{ di + 1 }}</text>
  20. </view>
  21. <view class="form-box">
  22. <view class="row">
  23. <text class="label">名称</text>
  24. <input class="input" v-model="dish.name" placeholder="请输入" placeholder-class="ph" />
  25. </view>
  26. <view class="row">
  27. <text class="label">价格</text>
  28. <input class="input" v-model="dish.price" type="digit" placeholder="输入金额" placeholder-class="ph" @blur="onPriceBlur(dish, $event)" />
  29. <text class="unit">元</text>
  30. </view>
  31. <view class="row last">
  32. <text class="label">描述</text>
  33. <input class="input" v-model="dish.description" placeholder="请描述菜品的使用素材" placeholder-class="ph" />
  34. </view>
  35. </view>
  36. <view class="ops">
  37. <view class="op-btn danger" @click.stop="removeItem(ci, di)">
  38. <u-icon name="trash" color="#FF4D4F" size="28"></u-icon>
  39. <text>删除</text>
  40. </view>
  41. <view
  42. v-if="di === cat.items.length - 1"
  43. class="op-btn primary"
  44. @click.stop="addItem(ci)"
  45. >
  46. <u-icon name="plus-square-fill" color="#449AFF" size="28"></u-icon>
  47. <text>添加</text>
  48. </view>
  49. </view>
  50. </view>
  51. </u-collapse-item>
  52. </u-collapse>
  53. </view>
  54. <view class="footer">
  55. <u-button type="primary" shape="circle" @click="onConfirm">确定</u-button>
  56. </view>
  57. </view>
  58. </template>
  59. <script>
  60. let itemIdSeed = 1;
  61. const nextItemId = () => ++itemIdSeed;
  62. const emptyItem = () => ({ id: nextItemId(), name: '', price: '', description: '' });
  63. export default {
  64. data() {
  65. return {
  66. categories: [
  67. { id: 'meat', name: '荤菜', items: [emptyItem()] },
  68. { id: 'veg', name: '素菜', items: [emptyItem()] },
  69. { id: 'staple', name: '主食', items: [emptyItem()] },
  70. { id: 'soup', name: '汤', items: [emptyItem()] },
  71. { id: 'other', name: '其他', items: [emptyItem()] }
  72. ],
  73. activeNames: ['meat']
  74. };
  75. },
  76. onLoad() {
  77. const prev = this.getPrevFormPage();
  78. const editing = prev && prev.form ? prev.form.comboItems : null;
  79. if (editing && editing.length) {
  80. this.categories = editing.map((cat) => ({
  81. ...cat,
  82. items: (cat.items && cat.items.length ? cat.items : [emptyItem()]).map((item) => ({
  83. ...item,
  84. id: item.id || nextItemId()
  85. }))
  86. }));
  87. this.categories.forEach((cat) => {
  88. (cat.items || []).forEach((item) => {
  89. if (Number(item.id) >= itemIdSeed) itemIdSeed = Number(item.id);
  90. });
  91. });
  92. }
  93. },
  94. methods: {
  95. onPriceBlur(dish, event) {
  96. const value = String((event.detail && event.detail.value) || '');
  97. dish.price = value.replace(/-/g, '');
  98. },
  99. getPrevFormPage() {
  100. const pages = getCurrentPages();
  101. const prev = pages[pages.length - 2];
  102. return prev && prev.$vm ? prev.$vm : null;
  103. },
  104. onCollapse(e) {
  105. this.activeNames = Array.isArray(e) ? e : e ? [e] : [];
  106. },
  107. filledCount(cat) {
  108. return (cat.items || []).filter((i) => i.name).length;
  109. },
  110. refreshCollapse() {
  111. this.$nextTick(() => {
  112. setTimeout(() => {
  113. if (this.$refs.collapse && this.$refs.collapse.init) {
  114. this.$refs.collapse.init();
  115. }
  116. }, 50);
  117. });
  118. },
  119. addItem(ci) {
  120. const items = this.categories[ci].items.slice();
  121. items.push(emptyItem());
  122. this.$set(this.categories[ci], 'items', items);
  123. this.refreshCollapse();
  124. },
  125. removeItem(ci, di) {
  126. const items = this.categories[ci].items.slice();
  127. if (items.length <= 1) {
  128. this.$set(this.categories[ci], 'items', [emptyItem()]);
  129. } else {
  130. items.splice(di, 1);
  131. this.$set(this.categories[ci], 'items', items);
  132. }
  133. this.refreshCollapse();
  134. },
  135. onConfirm() {
  136. const prev = this.getPrevFormPage();
  137. if (prev && typeof prev.applyComboItems === 'function') {
  138. prev.applyComboItems(this.categories);
  139. } else if (prev && prev.form) {
  140. prev.form.comboItems = this.categories;
  141. }
  142. uni.showToast({ title: '提交成功', icon: 'success' });
  143. setTimeout(() => uni.navigateBack(), 400);
  144. }
  145. }
  146. };
  147. </script>
  148. <style lang="scss" scoped>
  149. .page {
  150. min-height: 100vh;
  151. background: #f7f8fc;
  152. padding-bottom: 160rpx;
  153. }
  154. .content {
  155. padding: 20rpx 24rpx;
  156. }
  157. .c-title {
  158. font-size: 30rpx;
  159. font-weight: 500;
  160. color: #333;
  161. .num {
  162. color: #449aff;
  163. margin-left: 8rpx;
  164. }
  165. }
  166. .dish-item {
  167. margin-bottom: 24rpx;
  168. }
  169. .dish-head {
  170. display: flex;
  171. align-items: center;
  172. gap: 8rpx;
  173. margin-bottom: 12rpx;
  174. .dish-title {
  175. font-size: 28rpx;
  176. font-weight: 500;
  177. color: #333;
  178. }
  179. }
  180. .form-box {
  181. background: #edf5ff;
  182. border-radius: 12rpx;
  183. padding: 0 24rpx;
  184. overflow: hidden;
  185. }
  186. .row {
  187. display: flex;
  188. align-items: center;
  189. min-height: 88rpx;
  190. border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
  191. &.last {
  192. border-bottom: none;
  193. }
  194. .label {
  195. width: 88rpx;
  196. flex-shrink: 0;
  197. font-size: 28rpx;
  198. color: #333;
  199. }
  200. .input {
  201. flex: 1;
  202. font-size: 28rpx;
  203. color: #333;
  204. text-align: left;
  205. }
  206. .unit {
  207. margin-left: 8rpx;
  208. font-size: 28rpx;
  209. color: #333;
  210. flex-shrink: 0;
  211. }
  212. }
  213. .ph {
  214. color: #c0c4cc;
  215. }
  216. .ops {
  217. display: flex;
  218. justify-content: flex-end;
  219. align-items: center;
  220. gap: 16rpx;
  221. margin-top: 16rpx;
  222. }
  223. .op-btn {
  224. display: flex;
  225. align-items: center;
  226. justify-content: center;
  227. gap: 6rpx;
  228. height: 56rpx;
  229. padding: 0 22rpx;
  230. border-radius: 8rpx;
  231. font-size: 26rpx;
  232. box-sizing: border-box;
  233. &.danger {
  234. color: #ff4d4f;
  235. border: 1rpx solid #ff4d4f;
  236. background: #fff;
  237. }
  238. &.primary {
  239. color: #449aff;
  240. border: 1rpx solid #449aff;
  241. background: #fff;
  242. }
  243. }
  244. .footer {
  245. position: fixed;
  246. left: 0;
  247. right: 0;
  248. bottom: 0;
  249. padding: 20rpx 40rpx calc(20rpx + env(safe-area-inset-bottom));
  250. background: #fff;
  251. }
  252. </style>