meal-config.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <template>
  2. <view class="meal-config-page">
  3. <view class="nav-bar">
  4. <view class="nav-back" @tap="goBack">
  5. <text class="back-text">‹ 返回</text>
  6. </view>
  7. <text class="nav-title">共餐配置</text>
  8. <view class="nav-placeholder"></view>
  9. </view>
  10. <scroll-view class="content" scroll-y>
  11. <!-- 日期类型切换 -->
  12. <view class="date-tabs">
  13. <view class="tab-item" :class="{'tab-active': dateType === 'weekday'}" @tap="switchDateType('weekday')">
  14. <text>工作日</text>
  15. </view>
  16. <view class="tab-item" :class="{'tab-active': dateType === 'weekend'}" @tap="switchDateType('weekend')">
  17. <text>周末</text>
  18. </view>
  19. </view>
  20. <!-- 餐次配置 -->
  21. <view class="meal-section" v-for="meal in mealTypes" :key="meal.value">
  22. <view class="meal-header">
  23. <text class="meal-icon">{{ meal.icon }}</text>
  24. <text class="meal-name">{{ meal.label }}</text>
  25. </view>
  26. <view class="member-list">
  27. <view class="member-item" v-for="member in familyMembers" :key="member.id"
  28. :class="{'member-selected': isMemberSelected(meal.value, member.id)}"
  29. @tap="toggleMember(meal.value, member.id)">
  30. <text class="member-avatar">{{ member.nickname ? member.nickname.substring(0, 1) : '?' }}</text>
  31. <text class="member-name">{{ member.nickname || '未知' }}</text>
  32. </view>
  33. <view class="add-member-btn" @tap="addMember(meal.value)">
  34. <text>+</text>
  35. </view>
  36. </view>
  37. </view>
  38. <!-- 说明 -->
  39. <view class="tip-card">
  40. <text class="tip-text">💡 提示:选择与您在{{ dateType === 'weekday' ? '工作日' : '周末' }}共餐的家庭成员</text>
  41. </view>
  42. <!-- 保存按钮 -->
  43. <view class="save-area">
  44. <view class="save-btn" @tap="saveConfig">
  45. <text>{{ saving ? '保存中...' : '保存配置' }}</text>
  46. </view>
  47. </view>
  48. </scroll-view>
  49. </view>
  50. </template>
  51. <script>
  52. import config from '@/config.js'
  53. import { getMealConfig, saveMealConfig } from '@/utils/api.js'
  54. export default {
  55. data() {
  56. return {
  57. dateType: 'weekday',
  58. familyMembers: [],
  59. mealTypes: [
  60. { label: '早餐', value: 'breakfast', icon: '🌅' },
  61. { label: '午餐', value: 'lunch', icon: '☀️' },
  62. { label: '晚餐', value: 'dinner', icon: '🌙' }
  63. ],
  64. mealConfigs: {},
  65. saving: false
  66. }
  67. },
  68. onLoad: function() {
  69. this.loadMembers()
  70. this.loadConfigs()
  71. },
  72. methods: {
  73. goBack: function() {
  74. uni.navigateBack()
  75. },
  76. loadMembers: function() {
  77. var self = this
  78. var token = uni.getStorageSync('token')
  79. if (!token) return
  80. uni.request({
  81. url: this.getApiUrl('/api/family/member/list'),
  82. method: 'POST',
  83. data: {},
  84. header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
  85. success: function(res) {
  86. if (res.data && res.data.code === 200 && res.data.data) {
  87. self.familyMembers = res.data.data
  88. }
  89. }
  90. })
  91. },
  92. loadConfigs: function() {
  93. var self = this
  94. this.mealTypes.forEach(function(meal) {
  95. getMealConfig({ date_type: self.dateType, meal_type: meal.value }).then(function(config) {
  96. if (config) {
  97. self.mealConfigs[meal.value] = config
  98. }
  99. })
  100. })
  101. },
  102. switchDateType: function(type) {
  103. this.dateType = type
  104. this.loadConfigs()
  105. },
  106. isMemberSelected: function(mealType, memberId) {
  107. var config = this.mealConfigs[mealType]
  108. if (!config || !config.participantMemberIds) return false
  109. try {
  110. var ids = JSON.parse(config.participantMemberIds)
  111. return ids.indexOf(memberId) >= 0
  112. } catch (e) {
  113. return false
  114. }
  115. },
  116. toggleMember: function(mealType, memberId) {
  117. var config = this.mealConfigs[mealType] || {}
  118. var ids = []
  119. if (config.participantMemberIds) {
  120. try {
  121. ids = JSON.parse(config.participantMemberIds)
  122. } catch (e) {}
  123. }
  124. var index = ids.indexOf(memberId)
  125. if (index >= 0) {
  126. ids.splice(index, 1)
  127. } else {
  128. ids.push(memberId)
  129. }
  130. this.mealConfigs[mealType] = Object.assign({}, config, { participantMemberIds: JSON.stringify(ids) })
  131. },
  132. addMember: function(mealType) {
  133. // 简化的添加成员逻辑
  134. this.toggleMember(mealType, this.familyMembers[0] && this.familyMembers[0].id)
  135. },
  136. saveConfig: function() {
  137. var self = this
  138. this.saving = true
  139. var promises = this.mealTypes.map(function(meal) {
  140. var config = self.mealConfigs[meal.value] || {}
  141. return saveMealConfig({
  142. date_type: self.dateType,
  143. meal_type: meal.value,
  144. participant_member_ids: JSON.stringify(config.participantMemberIds || '[]')
  145. })
  146. })
  147. Promise.all(promises).then(function() {
  148. self.saving = false
  149. uni.showToast({ title: '保存成功', icon: 'success' })
  150. }).catch(function() {
  151. self.saving = false
  152. uni.showToast({ title: '保存失败', icon: 'none' })
  153. })
  154. },
  155. getApiUrl: function(path) {
  156. return config.api(path)
  157. }
  158. }
  159. }
  160. </script>
  161. <style scoped>
  162. .meal-config-page {
  163. min-height: 100vh;
  164. background-color: #F5F7FA;
  165. display: flex;
  166. flex-direction: column;
  167. }
  168. .nav-bar {
  169. display: flex;
  170. align-items: center;
  171. justify-content: center;
  172. height: 88rpx;
  173. padding-top: env(safe-area-inset-top);
  174. background-color: #FFFFFF;
  175. position: relative;
  176. border-bottom: 1rpx solid #f0f0f0;
  177. }
  178. .nav-back {
  179. position: absolute;
  180. left: 30rpx;
  181. top: 50%;
  182. transform: translateY(-50%);
  183. padding: 10rpx;
  184. }
  185. .back-text {
  186. font-size: 28rpx;
  187. color: #333;
  188. }
  189. .nav-title {
  190. font-size: 32rpx;
  191. font-weight: 600;
  192. color: #333;
  193. }
  194. .nav-placeholder {
  195. width: 80rpx;
  196. }
  197. .content {
  198. flex: 1;
  199. padding: 30rpx;
  200. }
  201. .date-tabs {
  202. display: flex;
  203. gap: 20rpx;
  204. margin-bottom: 30rpx;
  205. }
  206. .tab-item {
  207. flex: 1;
  208. padding: 20rpx 0;
  209. border: 1rpx solid #ddd;
  210. border-radius: 30rpx;
  211. text-align: center;
  212. font-size: 26rpx;
  213. color: #666;
  214. background-color: #FFFFFF;
  215. }
  216. .tab-active {
  217. border-color: #FF8C42;
  218. color: #FF8C42;
  219. background-color: #FFF5EB;
  220. font-weight: 600;
  221. }
  222. .meal-section {
  223. background-color: #FFFFFF;
  224. border-radius: 20rpx;
  225. margin-bottom: 30rpx;
  226. overflow: hidden;
  227. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
  228. }
  229. .meal-header {
  230. display: flex;
  231. align-items: center;
  232. padding: 24rpx 30rpx;
  233. border-bottom: 1rpx solid #f0f0f0;
  234. }
  235. .meal-icon {
  236. font-size: 36rpx;
  237. margin-right: 16rpx;
  238. }
  239. .meal-name {
  240. font-size: 28rpx;
  241. font-weight: 600;
  242. color: #333;
  243. }
  244. .member-list {
  245. display: flex;
  246. flex-wrap: wrap;
  247. gap: 16rpx;
  248. padding: 24rpx 30rpx;
  249. }
  250. .member-item {
  251. display: flex;
  252. flex-direction: column;
  253. align-items: center;
  254. padding: 16rpx 24rpx;
  255. border: 1rpx solid #ddd;
  256. border-radius: 16rpx;
  257. background-color: #FAFAFA;
  258. }
  259. .member-selected {
  260. border-color: #FF8C42;
  261. background-color: #FFF5EB;
  262. }
  263. .member-avatar {
  264. width: 60rpx;
  265. height: 60rpx;
  266. border-radius: 50%;
  267. background-color: #FF8C42;
  268. color: #FFFFFF;
  269. display: flex;
  270. align-items: center;
  271. justify-content: center;
  272. font-size: 24rpx;
  273. font-weight: 600;
  274. margin-bottom: 8rpx;
  275. }
  276. .member-name {
  277. font-size: 22rpx;
  278. color: #666;
  279. }
  280. .add-member-btn {
  281. width: 80rpx;
  282. height: 80rpx;
  283. border: 2rpx dashed #ddd;
  284. border-radius: 16rpx;
  285. display: flex;
  286. align-items: center;
  287. justify-content: center;
  288. font-size: 40rpx;
  289. color: #999;
  290. }
  291. .tip-card {
  292. background-color: #E3F2FD;
  293. border-radius: 16rpx;
  294. padding: 24rpx 30rpx;
  295. margin-bottom: 30rpx;
  296. }
  297. .tip-text {
  298. font-size: 24rpx;
  299. color: #1976D2;
  300. line-height: 1.6;
  301. }
  302. .save-area {
  303. padding: 40rpx 30rpx;
  304. }
  305. .save-btn {
  306. background-color: #FF8C42;
  307. color: #FFFFFF;
  308. text-align: center;
  309. padding: 28rpx 0;
  310. border-radius: 16rpx;
  311. font-size: 30rpx;
  312. font-weight: 600;
  313. }
  314. </style>