article-edit.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <template>
  2. <view class="edit-container">
  3. <!-- 自定义导航 -->
  4. <view class="edit-header">
  5. <view class="header-left" @click="onBack">
  6. <text class="back-icon">←</text>
  7. <text class="back-text">返回</text>
  8. </view>
  9. <text class="header-title">写成长记录</text>
  10. <view class="header-right" @click="goMyPosts">
  11. <text class="history-text">历史</text>
  12. </view>
  13. </view>
  14. <scroll-view scroll-y class="edit-form">
  15. <!-- 封面图 -->
  16. <view class="form-cover" @click="chooseCover">
  17. <image v-if="coverImage" :src="coverImage" mode="aspectFill" class="cover-preview" />
  18. <view v-else class="cover-placeholder">
  19. <text class="cover-icon">📷</text>
  20. <text class="cover-hint">添加封面</text>
  21. </view>
  22. <view v-if="coverImage" class="cover-change" @click.stop="chooseCover">
  23. <text class="change-text">更换</text>
  24. </view>
  25. </view>
  26. <!-- 标题 -->
  27. <view class="form-group">
  28. <input
  29. class="form-input title-input"
  30. v-model="title"
  31. placeholder="给这篇记录取个标题"
  32. maxlength="50"
  33. />
  34. <text class="char-count">{{ title.length }}/50</text>
  35. </view>
  36. <!-- 维度选择 -->
  37. <view class="form-group">
  38. <text class="form-label">关联维度(选填)</text>
  39. <view class="dimension-chips">
  40. <view
  41. v-for="dim in dimensionList"
  42. :key="dim.code"
  43. :class="['dim-chip', selectedDimensions.indexOf(dim.code) >= 0 ? 'active' : '']"
  44. :style="selectedDimensions.indexOf(dim.code) >= 0 ? 'background:' + dim.color + ';color:#fff;border-color:' + dim.color : ''"
  45. @click="toggleDimension(dim.code)"
  46. >
  47. {{ dim.name }}
  48. </view>
  49. </view>
  50. </view>
  51. <!-- 正文 -->
  52. <view class="form-group">
  53. <textarea
  54. class="form-textarea"
  55. v-model="content"
  56. placeholder="记录孩子的成长点滴..."
  57. maxlength="2000"
  58. />
  59. <text class="char-count">{{ content.length }}/2000</text>
  60. </view>
  61. <!-- 可见范围 -->
  62. <view class="form-group">
  63. <text class="form-label">可见范围</text>
  64. <view class="visibility-options">
  65. <view
  66. :class="['vis-option', visibility === 'private' ? 'active' : '']"
  67. @click="visibility = 'private'"
  68. >
  69. <text class="vis-icon">🏠</text>
  70. <text class="vis-text">仅家庭</text>
  71. <text class="vis-desc">仅家庭成员可见</text>
  72. </view>
  73. <view
  74. :class="['vis-option', visibility === 'public' ? 'active' : '']"
  75. @click="visibility = 'public'"
  76. >
  77. <text class="vis-icon">🌍</text>
  78. <text class="vis-text">公开发布</text>
  79. <text class="vis-desc">所有人可见,需审核</text>
  80. </view>
  81. </view>
  82. </view>
  83. <!-- 发布按钮 -->
  84. <button class="publish-btn" :disabled="!canPublish" @click="onPublish">
  85. {{ publishing ? '发布中...' : '发布' }}
  86. </button>
  87. <view style="height: 60rpx;"></view>
  88. </scroll-view>
  89. </view>
  90. </template>
  91. <script>
  92. import { publishArticle } from '@/utils/api.js'
  93. import config from '@/config.js'
  94. export default {
  95. data() {
  96. return {
  97. title: '',
  98. coverImage: '',
  99. content: '',
  100. visibility: 'private',
  101. selectedDimensions: [],
  102. publishing: false,
  103. dimensionList: [
  104. { code: 'body', name: '身', color: '#FF8C42' },
  105. { code: 'mind', name: '智', color: '#6366F1' },
  106. { code: 'wisdom', name: '心', color: '#FF6B9D' },
  107. { code: 'action', name: '行', color: '#10B981' },
  108. { code: 'wealth', name: '富', color: '#F59E0B' }
  109. ]
  110. }
  111. },
  112. computed: {
  113. canPublish: function() {
  114. return this.title.trim().length > 0 && this.content.trim().length > 0 && !this.publishing
  115. }
  116. },
  117. methods: {
  118. chooseCover() {
  119. var self = this
  120. uni.chooseImage({
  121. count: 1,
  122. sizeType: ['compressed'],
  123. sourceType: ['album', 'camera'],
  124. success: function(res) {
  125. var tempPath = res.tempFilePaths[0]
  126. // 上传图片
  127. var token = uni.getStorageSync('token')
  128. uni.uploadFile({
  129. url: (config.default && config.default.API_BASE_URL) || config.API_BASE_URL || '' + '/api/media/upload',
  130. filePath: tempPath,
  131. name: 'file',
  132. header: {
  133. 'Authorization': token ? 'Bearer ' + token : ''
  134. },
  135. success: function(uploadRes) {
  136. try {
  137. var data = JSON.parse(uploadRes.data)
  138. if (data && data.code === 200) {
  139. self.coverImage = data.data
  140. } else {
  141. uni.showToast({ title: '上传失败', icon: 'none' })
  142. }
  143. } catch (e) {
  144. uni.showToast({ title: '上传失败', icon: 'none' })
  145. }
  146. },
  147. fail: function() {
  148. uni.showToast({ title: '上传失败', icon: 'none' })
  149. }
  150. })
  151. }
  152. })
  153. },
  154. toggleDimension(code) {
  155. var idx = this.selectedDimensions.indexOf(code)
  156. if (idx >= 0) {
  157. this.selectedDimensions.splice(idx, 1)
  158. } else {
  159. this.selectedDimensions.push(code)
  160. }
  161. },
  162. onBack() {
  163. if (this.title || this.content) {
  164. var self = this
  165. uni.showModal({
  166. title: '提示',
  167. content: '是否保存草稿?',
  168. success: function(res) {
  169. if (res.confirm) {
  170. uni.setStorageSync('article_draft', {
  171. title: self.title,
  172. coverImage: self.coverImage,
  173. content: self.content,
  174. visibility: self.visibility,
  175. selectedDimensions: self.selectedDimensions
  176. })
  177. }
  178. uni.navigateBack()
  179. }
  180. })
  181. } else {
  182. uni.navigateBack()
  183. }
  184. },
  185. goMyPosts() {
  186. uni.navigateTo({ url: '/pages/article-center/my-posts' })
  187. },
  188. async onPublish() {
  189. if (!this.canPublish) return
  190. this.publishing = true
  191. try {
  192. var res = await publishArticle({
  193. title: this.title.trim(),
  194. content: this.content.trim(),
  195. coverImage: this.coverImage,
  196. visibility: this.visibility,
  197. relatedDimensions: this.selectedDimensions.join(',')
  198. })
  199. if (res.code === 200) {
  200. var msg = this.visibility === 'public'
  201. ? '发布成功,等待审核'
  202. : '发布成功,已对家庭可见'
  203. uni.showToast({ title: msg, icon: 'success', duration: 2000 })
  204. uni.removeStorageSync('article_draft')
  205. var self = this
  206. setTimeout(function() {
  207. uni.navigateBack()
  208. }, 1500)
  209. } else {
  210. uni.showToast({ title: res.message || '发布失败', icon: 'none' })
  211. }
  212. } catch (e) {
  213. uni.showToast({ title: '发布失败', icon: 'none' })
  214. } finally {
  215. this.publishing = false
  216. }
  217. }
  218. },
  219. onLoad() {
  220. // 加载草稿
  221. try {
  222. var draft = uni.getStorageSync('article_draft')
  223. if (draft) {
  224. this.title = draft.title || ''
  225. this.coverImage = draft.coverImage || ''
  226. this.content = draft.content || ''
  227. this.visibility = draft.visibility || 'private'
  228. this.selectedDimensions = draft.selectedDimensions || []
  229. }
  230. } catch (e) {}
  231. }
  232. }
  233. </script>
  234. <style scoped>
  235. .edit-container { min-height: 100vh; background: #f5f7fa; }
  236. .edit-header {
  237. display: flex;
  238. align-items: center;
  239. justify-content: space-between;
  240. padding: 80rpx 30rpx 20rpx;
  241. background: #fff;
  242. position: sticky;
  243. top: 0;
  244. z-index: 10;
  245. }
  246. .header-left { display: flex; align-items: center; }
  247. .back-icon { font-size: 36rpx; margin-right: 8rpx; }
  248. .back-text { font-size: 26rpx; color: #333; }
  249. .header-title { font-size: 32rpx; font-weight: bold; color: #333; }
  250. .header-right { }
  251. .history-text { font-size: 26rpx; color: #5B9BD5; }
  252. .edit-form { padding: 20rpx 30rpx; }
  253. /* 封面 */
  254. .form-cover {
  255. width: 100%;
  256. height: 300rpx;
  257. background: #eee;
  258. border-radius: 16rpx;
  259. overflow: hidden;
  260. margin-bottom: 20rpx;
  261. position: relative;
  262. }
  263. .cover-preview { width: 100%; height: 100%; }
  264. .cover-placeholder {
  265. display: flex;
  266. flex-direction: column;
  267. align-items: center;
  268. justify-content: center;
  269. height: 100%;
  270. }
  271. .cover-icon { font-size: 60rpx; margin-bottom: 10rpx; }
  272. .cover-hint { font-size: 24rpx; color: #999; }
  273. .cover-change {
  274. position: absolute;
  275. right: 16rpx;
  276. bottom: 16rpx;
  277. background: rgba(0,0,0,0.5);
  278. color: #fff;
  279. padding: 6rpx 20rpx;
  280. border-radius: 20rpx;
  281. font-size: 22rpx;
  282. }
  283. /* 表单组 */
  284. .form-group {
  285. background: #fff;
  286. border-radius: 16rpx;
  287. padding: 24rpx;
  288. margin-bottom: 20rpx;
  289. }
  290. .form-input {
  291. width: 100%;
  292. font-size: 30rpx;
  293. color: #333;
  294. border: none;
  295. outline: none;
  296. }
  297. .title-input { font-weight: bold; }
  298. .char-count { display: block; text-align: right; font-size: 20rpx; color: #bbb; margin-top: 8rpx; }
  299. .form-label { font-size: 24rpx; color: #666; display: block; margin-bottom: 16rpx; }
  300. /* 维度 */
  301. .dimension-chips { display: flex; flex-wrap: wrap; gap: 16rpx; }
  302. .dim-chip {
  303. padding: 10rpx 28rpx;
  304. border-radius: 30rpx;
  305. font-size: 24rpx;
  306. border: 2rpx solid #ddd;
  307. color: #666;
  308. }
  309. .dim-chip.active { color: #fff; border-color: transparent; }
  310. /* 文本框 */
  311. .form-textarea {
  312. width: 100%;
  313. height: 300rpx;
  314. font-size: 28rpx;
  315. color: #333;
  316. line-height: 1.6;
  317. border: none;
  318. outline: none;
  319. resize: none;
  320. }
  321. /* 可见范围 */
  322. .visibility-options { display: flex; gap: 20rpx; }
  323. .vis-option {
  324. flex: 1;
  325. padding: 24rpx;
  326. border-radius: 12rpx;
  327. border: 2rpx solid #eee;
  328. text-align: center;
  329. }
  330. .vis-option.active { border-color: #5B9BD5; background: #E8F4FD; }
  331. .vis-icon { font-size: 40rpx; display: block; margin-bottom: 8rpx; }
  332. .vis-text { font-size: 26rpx; font-weight: bold; color: #333; display: block; }
  333. .vis-desc { font-size: 20rpx; color: #999; display: block; margin-top: 4rpx; }
  334. /* 发布按钮 */
  335. .publish-btn {
  336. width: 100%;
  337. height: 88rpx;
  338. line-height: 88rpx;
  339. background: linear-gradient(135deg, #F97316, #EA580C);
  340. color: #fff;
  341. font-size: 32rpx;
  342. font-weight: bold;
  343. border-radius: 44rpx;
  344. text-align: center;
  345. border: none;
  346. margin-top: 20rpx;
  347. }
  348. .publish-btn[disabled] { opacity: 0.4; }
  349. .publish-btn::after { border: none; }
  350. </style>