create-circle.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <template>
  2. <view class="create-circle-page" :style="{ '--font-scale': fontScale }">
  3. <view class="nav-bar">
  4. <view class="nav-back" @tap="goBack"><text class="nav-back-icon">‹</text></view>
  5. <text class="nav-title">创建圈子</text>
  6. <view class="nav-placeholder"></view>
  7. </view>
  8. <scroll-view class="content" scroll-y>
  9. <view class="form-card">
  10. <view class="form-item">
  11. <text class="form-label">圈子名称 <text class="required">*</text></text>
  12. <input class="form-input" placeholder="给圈子起个名字" v-model="form.name" maxlength="30" />
  13. </view>
  14. <view class="form-item">
  15. <text class="form-label">圈子类型</text>
  16. <view class="type-grid">
  17. <view class="type-opt" :class="{ 'type-active': form.type === t.value }" v-for="t in circleTypes" :key="t.value" @tap="form.type = t.value">
  18. <text class="to-icon">{{ t.icon }}</text>
  19. <text class="to-label">{{ t.label }}</text>
  20. </view>
  21. </view>
  22. </view>
  23. <view class="form-item">
  24. <text class="form-label">圈子描述</text>
  25. <textarea class="form-textarea" placeholder="简单介绍一下这个圈子..." v-model="form.description" maxlength="200" />
  26. </view>
  27. <view class="form-item">
  28. <text class="form-label">加入方式</text>
  29. <view class="join-options">
  30. <view class="join-opt" :class="{ 'join-active': form.joinCondition === 0 }" @tap="form.joinCondition = 0">
  31. <text class="jo-icon">🌐</text>
  32. <text class="jo-label">公开加入</text>
  33. <text class="jo-desc">任何人都可以加入</text>
  34. </view>
  35. <view class="join-opt" :class="{ 'join-active': form.joinCondition === 1 }" @tap="form.joinCondition = 1">
  36. <text class="jo-icon">🔒</text>
  37. <text class="jo-label">需审核</text>
  38. <text class="jo-desc">管理员审核后加入</text>
  39. </view>
  40. <view class="join-opt" :class="{ 'join-active': form.joinCondition === 2 }" @tap="form.joinCondition = 2">
  41. <text class="jo-icon">🔗</text>
  42. <text class="jo-label">邀请制</text>
  43. <text class="jo-desc">仅邀请可加入</text>
  44. </view>
  45. </view>
  46. </view>
  47. <view class="form-item">
  48. <view class="switch-row">
  49. <text class="form-label">公开可见</text>
  50. <switch :checked="form.isPublic === 1" :color="bodyColor" @change="form.isPublic = $event.detail.value ? 1 : 0" />
  51. </view>
  52. <text class="switch-desc">其他用户可以在发现页看到此圈子</text>
  53. </view>
  54. </view>
  55. <view class="bottom-space"></view>
  56. </scroll-view>
  57. <view class="fixed-bottom">
  58. <button class="btn-create" :disabled="!form.name.trim()" @tap="handleCreate">创建圈子</button>
  59. </view>
  60. </view>
  61. </template>
  62. <script>
  63. import { BrandColors } from '@/utils/brand-colors.js'
  64. import { createGeneralCircle } from '@/utils/api.js'
  65. import fontScaleMixin from '@/mixins/font-scale.js'
  66. export default {
  67. mixins: [fontScaleMixin],
  68. data: function() {
  69. return {
  70. bodyColor: BrandColors.body,
  71. form: {
  72. name: '',
  73. type: 'topic',
  74. description: '',
  75. joinCondition: 0,
  76. isPublic: 1
  77. },
  78. circleTypes: [
  79. { value: 'topic', label: '话题', icon: '💬' },
  80. { value: 'activity', label: '活动', icon: '🎯' },
  81. { value: 'hobby', label: '爱好', icon: '🎨' },
  82. { value: 'health', label: '健康', icon: '💪' },
  83. { value: 'ability', label: '能力', icon: '🧠' },
  84. { value: 'product', label: '商品', icon: '🛒' }
  85. ]
  86. }
  87. },
  88. methods: {
  89. goBack: function() {
  90. uni.navigateBack()
  91. },
  92. handleCreate: function() {
  93. var self = this
  94. if (!self.form.name.trim()) return
  95. var memberId = uni.getStorageSync('currentChildId') || ''
  96. if (!memberId) {
  97. uni.showToast({ title: '请先登录', icon: 'none' })
  98. return
  99. }
  100. uni.showLoading({ title: '创建中...' })
  101. createGeneralCircle({
  102. name: self.form.name.trim(),
  103. type: self.form.type,
  104. description: self.form.description,
  105. joinCondition: self.form.joinCondition,
  106. isPublic: self.form.isPublic,
  107. creatorId: memberId,
  108. creatorType: 'child'
  109. }).then(function(res) {
  110. uni.hideLoading()
  111. if (res.code === 200 && res.data) {
  112. uni.showToast({ title: '创建成功', icon: 'success' })
  113. setTimeout(function() {
  114. uni.redirectTo({ url: '/pages/discover/circle-feed?circleId=' + res.data.id + '&name=' + encodeURIComponent(self.form.name) + '&memberCount=1' })
  115. }, 1000)
  116. } else {
  117. uni.showToast({ title: res.message || '创建失败', icon: 'none' })
  118. }
  119. }).catch(function() {
  120. uni.hideLoading()
  121. uni.showToast({ title: '创建失败,请重试', icon: 'none' })
  122. })
  123. }
  124. }
  125. }
  126. </script>
  127. <style scoped lang="scss">
  128. .create-circle-page { min-height: 100vh; background: $app-bg; padding-bottom: 160rpx; }
  129. .nav-bar {
  130. display: flex; align-items: center; justify-content: space-between;
  131. height: 88rpx; padding-top: env(safe-area-inset-top);
  132. background: $app-surface; position: sticky; top: 0; z-index: 100;
  133. border-bottom: 1rpx solid $facade-fill;
  134. }
  135. .nav-back { width: 80rpx; padding-left: 24rpx; }
  136. .nav-back-icon { font-size: calc(48rpx * var(--font-scale, 1)); color: $facade-text-strong; }
  137. .nav-title { font-size: calc(32rpx * var(--font-scale, 1)); font-weight: 600; color: $facade-text-strong; }
  138. .nav-placeholder { width: 80rpx; }
  139. .content { padding: 24rpx 32rpx; }
  140. .form-card { background: $app-surface; border-radius: 24rpx; padding: 24rpx; }
  141. .form-item { padding: 24rpx 0; border-bottom: 1rpx solid $facade-fill-2; }
  142. .form-item:last-child { border-bottom: none; }
  143. .form-label { font-size: calc(28rpx * var(--font-scale, 1)); font-weight: 600; color: $facade-text-strong; display: block; margin-bottom: 16rpx; }
  144. .required { color: $mat-red; }
  145. .form-input {
  146. width: 100%; height: 72rpx; background: $facade-fill-2; border-radius: 16rpx;
  147. padding: 0 20rpx; font-size: calc(28rpx * var(--font-scale, 1)); box-sizing: border-box;
  148. }
  149. .form-textarea {
  150. width: 100%; height: 160rpx; background: $facade-fill-2; border-radius: 16rpx;
  151. padding: 16rpx 20rpx; font-size: calc(26rpx * var(--font-scale, 1)); line-height: 1.6; box-sizing: border-box;
  152. }
  153. .type-grid { display: flex; flex-wrap: wrap; }
  154. .type-opt {
  155. display: flex; flex-direction: column; align-items: center;
  156. padding: 20rpx 28rpx; margin-right: 16rpx; margin-bottom: 16rpx;
  157. background: $facade-fill-2; border-radius: 16rpx; border: 2rpx solid transparent;
  158. }
  159. .type-active { border-color: $dim-body; background: $warm-peach; }
  160. .to-icon { font-size: calc(40rpx * var(--font-scale, 1)); margin-bottom: 8rpx; }
  161. .to-label { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-body; }
  162. .join-options { display: flex; flex-direction: column; }
  163. .join-opt {
  164. display: flex; align-items: center; padding: 20rpx;
  165. background: $facade-fill-ghost; border-radius: 16rpx; border: 2rpx solid transparent;
  166. }
  167. .join-active { border-color: $dim-body; background: $dim-body-tint; }
  168. .jo-icon { font-size: calc(36rpx * var(--font-scale, 1)); margin-right: 16rpx; }
  169. .jo-label { font-size: calc(26rpx * var(--font-scale, 1)); font-weight: 600; color: $facade-text-strong; margin-right: 16rpx; }
  170. .jo-desc { font-size: calc(22rpx * var(--font-scale, 1)); color: $facade-text-muted; }
  171. .switch-row { display: flex; align-items: center; justify-content: space-between; }
  172. .switch-desc { font-size: calc(22rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 8rpx; display: block; }
  173. .fixed-bottom {
  174. position: fixed; bottom: 0; left: 0; right: 0;
  175. padding: 20rpx 32rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  176. background: $app-surface; border-top: 1rpx solid $facade-fill;
  177. }
  178. .btn-create {
  179. background: linear-gradient(135deg, $dim-body, $dim-body-light);
  180. color: $app-surface; border: none; border-radius: 24rpx;
  181. font-size: calc(30rpx * var(--font-scale, 1)); font-weight: 600; padding: 24rpx 0;
  182. }
  183. .btn-create[disabled] { opacity: 0.4; }
  184. .bottom-space { height: 40rpx; }
  185. </style>