meal-config.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. var familyId = uni.getStorageSync('familyId')
  80. if (!token || !familyId) return
  81. uni.request({
  82. url: this.getApiUrl('/api/family/member/list'),
  83. method: 'POST',
  84. data: { familyId: familyId },
  85. header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
  86. success: function(res) {
  87. if (res.data && res.data.code === 200 && res.data.data) {
  88. self.familyMembers = res.data.data
  89. }
  90. }
  91. })
  92. },
  93. loadConfigs: function() {
  94. var self = this
  95. this.mealTypes.forEach(function(meal) {
  96. getMealConfig({ date_type: self.dateType, meal_type: meal.value }).then(function(config) {
  97. if (config) {
  98. self.mealConfigs[meal.value] = config
  99. }
  100. })
  101. })
  102. },
  103. switchDateType: function(type) {
  104. this.dateType = type
  105. this.loadConfigs()
  106. },
  107. isMemberSelected: function(mealType, memberId) {
  108. var config = this.mealConfigs[mealType]
  109. if (!config || !config.participantMemberIds) return false
  110. try {
  111. var ids = JSON.parse(config.participantMemberIds)
  112. return ids.indexOf(memberId) >= 0
  113. } catch (e) {
  114. return false
  115. }
  116. },
  117. toggleMember: function(mealType, memberId) {
  118. var config = this.mealConfigs[mealType] || {}
  119. var ids = []
  120. if (config.participantMemberIds) {
  121. try {
  122. ids = JSON.parse(config.participantMemberIds)
  123. } catch (e) {}
  124. }
  125. var index = ids.indexOf(memberId)
  126. if (index >= 0) {
  127. ids.splice(index, 1)
  128. } else {
  129. ids.push(memberId)
  130. }
  131. this.mealConfigs[mealType] = Object.assign({}, config, { participantMemberIds: JSON.stringify(ids) })
  132. },
  133. addMember: function(mealType) {
  134. // 简化的添加成员逻辑
  135. this.toggleMember(mealType, this.familyMembers[0] && this.familyMembers[0].id)
  136. },
  137. saveConfig: function() {
  138. var self = this
  139. this.saving = true
  140. var familyId = uni.getStorageSync('familyId')
  141. var promises = this.mealTypes.map(function(meal) {
  142. var config = self.mealConfigs[meal.value] || {}
  143. return saveMealConfig({
  144. family_id: familyId,
  145. date_type: self.dateType,
  146. meal_type: meal.value,
  147. participant_member_ids: JSON.stringify(config.participantMemberIds || '[]')
  148. })
  149. })
  150. Promise.all(promises).then(function() {
  151. self.saving = false
  152. uni.showToast({ title: '保存成功', icon: 'success' })
  153. }).catch(function() {
  154. self.saving = false
  155. uni.showToast({ title: '保存失败', icon: 'none' })
  156. })
  157. },
  158. getApiUrl: function(path) {
  159. return config.api(path)
  160. }
  161. }
  162. }
  163. </script>
  164. <style scoped>
  165. .meal-config-page {
  166. min-height: 100vh;
  167. background-color: #F5F7FA;
  168. display: flex;
  169. flex-direction: column;
  170. }
  171. .nav-bar {
  172. display: flex;
  173. align-items: center;
  174. justify-content: center;
  175. height: 88rpx;
  176. padding-top: env(safe-area-inset-top);
  177. background-color: #FFFFFF;
  178. position: relative;
  179. border-bottom: 1rpx solid #f0f0f0;
  180. }
  181. .nav-back {
  182. position: absolute;
  183. left: 30rpx;
  184. top: 50%;
  185. transform: translateY(-50%);
  186. padding: 10rpx;
  187. }
  188. .back-text {
  189. font-size: 28rpx;
  190. color: #333;
  191. }
  192. .nav-title {
  193. font-size: 32rpx;
  194. font-weight: 600;
  195. color: #333;
  196. }
  197. .nav-placeholder {
  198. width: 80rpx;
  199. }
  200. .content {
  201. flex: 1;
  202. padding: 30rpx;
  203. }
  204. .date-tabs {
  205. display: flex;
  206. gap: 20rpx;
  207. margin-bottom: 30rpx;
  208. }
  209. .tab-item {
  210. flex: 1;
  211. padding: 20rpx 0;
  212. border: 1rpx solid #ddd;
  213. border-radius: 30rpx;
  214. text-align: center;
  215. font-size: 26rpx;
  216. color: #666;
  217. background-color: #FFFFFF;
  218. }
  219. .tab-active {
  220. border-color: #FF8C42;
  221. color: #FF8C42;
  222. background-color: #FFF5EB;
  223. font-weight: 600;
  224. }
  225. .meal-section {
  226. background-color: #FFFFFF;
  227. border-radius: 20rpx;
  228. margin-bottom: 30rpx;
  229. overflow: hidden;
  230. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
  231. }
  232. .meal-header {
  233. display: flex;
  234. align-items: center;
  235. padding: 24rpx 30rpx;
  236. border-bottom: 1rpx solid #f0f0f0;
  237. }
  238. .meal-icon {
  239. font-size: 36rpx;
  240. margin-right: 16rpx;
  241. }
  242. .meal-name {
  243. font-size: 28rpx;
  244. font-weight: 600;
  245. color: #333;
  246. }
  247. .member-list {
  248. display: flex;
  249. flex-wrap: wrap;
  250. gap: 16rpx;
  251. padding: 24rpx 30rpx;
  252. }
  253. .member-item {
  254. display: flex;
  255. flex-direction: column;
  256. align-items: center;
  257. padding: 16rpx 24rpx;
  258. border: 1rpx solid #ddd;
  259. border-radius: 16rpx;
  260. background-color: #FAFAFA;
  261. }
  262. .member-selected {
  263. border-color: #FF8C42;
  264. background-color: #FFF5EB;
  265. }
  266. .member-avatar {
  267. width: 60rpx;
  268. height: 60rpx;
  269. border-radius: 50%;
  270. background-color: #FF8C42;
  271. color: #FFFFFF;
  272. display: flex;
  273. align-items: center;
  274. justify-content: center;
  275. font-size: 24rpx;
  276. font-weight: 600;
  277. margin-bottom: 8rpx;
  278. }
  279. .member-name {
  280. font-size: 22rpx;
  281. color: #666;
  282. }
  283. .add-member-btn {
  284. width: 80rpx;
  285. height: 80rpx;
  286. border: 2rpx dashed #ddd;
  287. border-radius: 16rpx;
  288. display: flex;
  289. align-items: center;
  290. justify-content: center;
  291. font-size: 40rpx;
  292. color: #999;
  293. }
  294. .tip-card {
  295. background-color: #E3F2FD;
  296. border-radius: 16rpx;
  297. padding: 24rpx 30rpx;
  298. margin-bottom: 30rpx;
  299. }
  300. .tip-text {
  301. font-size: 24rpx;
  302. color: #1976D2;
  303. line-height: 1.6;
  304. }
  305. .save-area {
  306. padding: 40rpx 30rpx;
  307. }
  308. .save-btn {
  309. background-color: #FF8C42;
  310. color: #FFFFFF;
  311. text-align: center;
  312. padding: 28rpx 0;
  313. border-radius: 16rpx;
  314. font-size: 30rpx;
  315. font-weight: 600;
  316. }
  317. </style>