portrait-edit.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <template>
  2. <view class="portrait-edit-page">
  3. <view class="section">
  4. <view class="section-title">我的画像描述</view>
  5. <view class="hint">请输入你的个人画像描述,AI 会根据这些信息提供更有针对性的建议(最多 2000 字)</view>
  6. <!-- 操作按钮行 -->
  7. <view class="action-row">
  8. <button class="action-btn" :loading="generating" @click="onGenerate" v-if="!portraitPrompt">
  9. 🪄 AI 生成画像
  10. </button>
  11. <button class="action-btn secondary" :loading="regenerating" @click="onRegenerate" v-if="portraitPrompt">
  12. 🔄 重画画像
  13. </button>
  14. </view>
  15. <textarea
  16. class="portrait-textarea"
  17. :value="portraitPrompt"
  18. placeholder="例如:我家孩子 8 岁,偏瘦,挑食,容易积食,睡眠不足..."
  19. maxlength="2000"
  20. @input="onInput"
  21. ></textarea>
  22. <view class="char-count">{{ portraitPrompt.length }}/2000</view>
  23. </view>
  24. <view class="section" v-if="renderedSnapshot">
  25. <view class="section-title">参考指标(自动生成)</view>
  26. <view class="snapshot-text">{{ renderedSnapshot }}</view>
  27. </view>
  28. <button class="save-btn" :loading="saving" @click="onSave">保存</button>
  29. </view>
  30. </template>
  31. <script>
  32. import { getPortrait, editPortrait, generatePortrait, regeneratePortrait } from '../../utils/api.js'
  33. export default {
  34. data() {
  35. return {
  36. portraitPrompt: '',
  37. renderedSnapshot: '',
  38. saving: false,
  39. generating: false,
  40. regenerating: false
  41. }
  42. },
  43. onLoad() {
  44. this.loadPortrait()
  45. },
  46. methods: {
  47. async loadPortrait() {
  48. try {
  49. const res = await getPortrait({ memberId: this.$store.state.currentMemberId })
  50. if (res.code === 200 && res.data) {
  51. this.portraitPrompt = res.data.portraitPrompt || ''
  52. this.renderedSnapshot = res.data.renderedSnapshot || ''
  53. }
  54. } catch (e) {
  55. console.error('加载画像失败', e)
  56. }
  57. },
  58. onInput(e) {
  59. this.portraitPrompt = e.detail.value
  60. },
  61. async onGenerate() {
  62. this.generating = true
  63. try {
  64. const memberId = this.$store.state.currentMemberId
  65. const res = await generatePortrait({ memberId })
  66. if (res.code === 200 && res.data) {
  67. this.portraitPrompt = res.data.portraitPrompt || ''
  68. this.renderedSnapshot = res.data.renderedSnapshot || ''
  69. uni.showToast({ title: '生成成功', icon: 'success' })
  70. } else {
  71. uni.showToast({ title: res.message || '生成失败', icon: 'none' })
  72. }
  73. } catch (e) {
  74. uni.showToast({ title: '生成失败', icon: 'none' })
  75. } finally {
  76. this.generating = false
  77. }
  78. },
  79. async onRegenerate() {
  80. this.regenerating = true
  81. try {
  82. const memberId = this.$store.state.currentMemberId
  83. const res = await regeneratePortrait({
  84. memberId,
  85. previousPortrait: this.portraitPrompt // 传入当前画像作为参考
  86. })
  87. if (res.code === 200 && res.data) {
  88. this.portraitPrompt = res.data.portraitPrompt || ''
  89. this.renderedSnapshot = res.data.renderedSnapshot || ''
  90. uni.showToast({ title: '重画成功', icon: 'success' })
  91. } else {
  92. uni.showToast({ title: res.message || '重画失败', icon: 'none' })
  93. }
  94. } catch (e) {
  95. uni.showToast({ title: '重画失败', icon: 'none' })
  96. } finally {
  97. this.regenerating = false
  98. }
  99. },
  100. async onSave() {
  101. this.saving = true
  102. try {
  103. const res = await editPortrait({ portraitPrompt: this.portraitPrompt })
  104. if (res.code === 200) {
  105. uni.showToast({ title: '保存成功', icon: 'success' })
  106. this.loadPortrait()
  107. } else {
  108. uni.showToast({ title: res.message || '保存失败', icon: 'none' })
  109. }
  110. } catch (e) {
  111. uni.showToast({ title: '保存失败', icon: 'none' })
  112. } finally {
  113. this.saving = false
  114. }
  115. }
  116. }
  117. }
  118. </script>
  119. <style scoped>
  120. .portrait-edit-page {
  121. padding: 24rpx;
  122. min-height: 100vh;
  123. background: #f5f5f5;
  124. }
  125. .section {
  126. background: #fff;
  127. border-radius: 16rpx;
  128. padding: 24rpx;
  129. margin-bottom: 24rpx;
  130. }
  131. .section-title {
  132. font-size: 32rpx;
  133. font-weight: bold;
  134. margin-bottom: 12rpx;
  135. color: #333;
  136. }
  137. .hint {
  138. font-size: 24rpx;
  139. color: #999;
  140. margin-bottom: 16rpx;
  141. }
  142. .action-row {
  143. display: flex;
  144. gap: 16rpx;
  145. margin-bottom: 16rpx;
  146. }
  147. .action-btn {
  148. flex: 1;
  149. height: 72rpx;
  150. border-radius: 36rpx;
  151. font-size: 26rpx;
  152. background: linear-gradient(135deg, #667eea, #764ba2);
  153. color: #fff;
  154. }
  155. .action-btn.secondary {
  156. background: linear-gradient(135deg, #F97316, #F59E0B);
  157. }
  158. .portrait-textarea {
  159. width: 100%;
  160. height: 300rpx;
  161. border: 1rpx solid #eee;
  162. border-radius: 8rpx;
  163. padding: 16rpx;
  164. font-size: 28rpx;
  165. box-sizing: border-box;
  166. line-height: 1.6;
  167. }
  168. .char-count {
  169. text-align: right;
  170. font-size: 22rpx;
  171. color: #999;
  172. margin-top: 8rpx;
  173. }
  174. .snapshot-text {
  175. font-size: 26rpx;
  176. color: #555;
  177. white-space: pre-wrap;
  178. line-height: 1.6;
  179. }
  180. .save-btn {
  181. background: #F97316;
  182. color: #fff;
  183. border-radius: 44rpx;
  184. height: 88rpx;
  185. line-height: 88rpx;
  186. font-size: 32rpx;
  187. margin-top: 32rpx;
  188. }
  189. </style>