Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

349 рядки
8.0 KiB

  1. <template>
  2. <view class="page">
  3. <scroll-view
  4. class="scroll"
  5. scroll-y
  6. refresher-enabled
  7. :refresher-triggered="refreshing"
  8. @refresherrefresh="refresh"
  9. @scrolltolower="loadMore"
  10. >
  11. <view class="list">
  12. <view v-for="item in list" :key="item.chat_open_id" class="item" @click="openItem(item)">
  13. <view class="item-head">
  14. <view class="title">{{ item.question_content || 'AI取名' }}</view>
  15. <text class="status" :class="{ pending: Number(item.is_suc) === 0, failed: Number(item.is_suc) < 0 }">
  16. {{ getStatusText(item) }}
  17. </text>
  18. </view>
  19. <view v-if="getNames(item).length" class="name-row">
  20. <text v-for="name in getNames(item)" :key="name" class="name-chip">{{ name }}</text>
  21. </view>
  22. <view class="desc">{{ getSummary(item) }}</view>
  23. <view class="meta">
  24. <text>{{ formatTime(item.add_time) }}</text>
  25. <text class="del" @click.stop="deleteItem(item)">删除</text>
  26. </view>
  27. </view>
  28. </view>
  29. <view v-if="!list.length && !loading" class="empty">暂无历史记录</view>
  30. <view v-if="loading" class="loading">加载中...</view>
  31. </scroll-view>
  32. </view>
  33. </template>
  34. <script setup>
  35. import { ref } from 'vue'
  36. import { onLoad, onUnload } from '@dcloudio/uni-app'
  37. import dayjs from 'dayjs'
  38. import { toolApi } from '@/api/index.js'
  39. import { useApp } from '@/utils/useApp.js'
  40. const { toast } = useApp()
  41. const robotId = ref('')
  42. const list = ref([])
  43. const page = ref(1)
  44. const total = ref(0)
  45. const loading = ref(false)
  46. const refreshing = ref(false)
  47. let timer = 0
  48. onLoad((options) => {
  49. robotId.value = options.robot_id || ''
  50. refresh()
  51. })
  52. onUnload(() => clearTimeout(timer))
  53. function formatTime(value) {
  54. if (!value) return ''
  55. return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm')
  56. }
  57. async function fetchList(reset = false) {
  58. if (loading.value) return
  59. loading.value = true
  60. try {
  61. const res = await toolApi.getNameList({
  62. page_no: page.value,
  63. page_size: 10,
  64. robot_open_id: robotId.value,
  65. role: 2
  66. })
  67. const rows = res.list || []
  68. total.value = Number((res.data && res.data.total) || rows.length)
  69. list.value = reset ? rows : list.value.concat(rows)
  70. scheduleRefresh()
  71. } catch (e) {
  72. toast(e.msg || '历史记录加载失败')
  73. } finally {
  74. loading.value = false
  75. refreshing.value = false
  76. }
  77. }
  78. function scheduleRefresh() {
  79. clearTimeout(timer)
  80. if (list.value.some((item) => Number(item.is_suc) === 0)) {
  81. timer = setTimeout(() => refreshSilent(), 2500)
  82. }
  83. }
  84. function refresh() {
  85. refreshing.value = true
  86. refreshSilent()
  87. }
  88. function refreshSilent() {
  89. page.value = 1
  90. fetchList(true)
  91. }
  92. function loadMore() {
  93. if (loading.value || list.value.length >= total.value) return
  94. page.value += 1
  95. fetchList()
  96. }
  97. function openItem(item) {
  98. uni.navigateTo({
  99. url: `/pages/tool/name-result?id=${item.chat_open_id}&robot_id=${robotId.value}&key=naming`
  100. })
  101. }
  102. function getStatusText(item) {
  103. if (Number(item.is_suc) === 0) return '生成中'
  104. if (Number(item.is_suc) < 0) return '失败'
  105. return '完成'
  106. }
  107. function getParsed(item) {
  108. if (item._parsed) return item._parsed
  109. item._parsed = parseResult(item.content)
  110. return item._parsed
  111. }
  112. function getNames(item) {
  113. const parsed = getParsed(item)
  114. return (parsed.names || []).map((name) => name.name).filter(Boolean).slice(0, 3)
  115. }
  116. function getSummary(item) {
  117. if (Number(item.is_suc) === 0) return '正在生成取名方案,请稍后查看'
  118. if (Number(item.is_suc) < 0) return item.content || '生成失败'
  119. const parsed = getParsed(item)
  120. if (parsed.summary) return parsed.summary
  121. const first = parsed.names && parsed.names[0]
  122. if (first) return [first.meaning, first.reason].filter(Boolean).join(' ')
  123. return '点击查看取名结果'
  124. }
  125. function parseResult(content) {
  126. if (!content) return { summary: '', names: [] }
  127. let text = String(content).trim()
  128. text = text.replace(/^```json\s*/i, '').replace(/^```\s*/i, '').replace(/```$/i, '').trim()
  129. const lineResult = parseJsonLines(text)
  130. if (lineResult.names.length) return lineResult
  131. const markedResult = parseMarkedText(text)
  132. if (markedResult.names.length) return markedResult
  133. const start = text.indexOf('{')
  134. const end = text.lastIndexOf('}')
  135. if (start >= 0 && end > start) text = text.substring(start, end + 1)
  136. try {
  137. const data = JSON.parse(text)
  138. return {
  139. summary: data.summary || '',
  140. names: Array.isArray(data.names) ? data.names : []
  141. }
  142. } catch (e) {
  143. return { summary: '', names: [] }
  144. }
  145. }
  146. function parseMarkedText(text) {
  147. const data = { summary: '', names: [] }
  148. const value = String(text || '')
  149. const summaryMatch = value.match(/整体建议[::]([\s\S]*?)(?=\n\s*【|$)/)
  150. if (summaryMatch) data.summary = summaryMatch[1].trim()
  151. const reg = /【([^】]+)】([\s\S]*?)(?=\n\s*【|$)/g
  152. let match
  153. while ((match = reg.exec(value))) {
  154. const body = match[2] || ''
  155. data.names.push({
  156. name: match[1].trim(),
  157. pinyin: matchField(body, '拼音'),
  158. score: parseInt(matchField(body, '评分')) || 0,
  159. source: matchField(body, '出处'),
  160. meaning: matchField(body, '寓意'),
  161. tone: matchField(body, '音律'),
  162. reason: matchField(body, '推荐')
  163. })
  164. }
  165. data.names = data.names.filter((item) => item.name).slice(0, 5)
  166. return data
  167. }
  168. function matchField(body, label) {
  169. const reg = new RegExp(`${label}[::]([^\\n\\r]*)`)
  170. const match = String(body || '').match(reg)
  171. return match ? match[1].trim() : ''
  172. }
  173. function parseJsonLines(text) {
  174. const data = { summary: '', names: [] }
  175. String(text || '').split(/\r?\n/).forEach((line) => {
  176. line = line.trim()
  177. if (!line) return
  178. try {
  179. const item = JSON.parse(line)
  180. if (item.type === 'summary') data.summary = item.content || ''
  181. if (item.type === 'name' && item.data) data.names.push(item.data)
  182. } catch (e) {}
  183. })
  184. return data
  185. }
  186. function deleteItem(item) {
  187. uni.showModal({
  188. title: '温馨提示',
  189. content: '是否确认删除该内容?',
  190. confirmText: '确认删除',
  191. success: async (res) => {
  192. if (!res.confirm) return
  193. try {
  194. await toolApi.deleteName({ chat_open_id: item.chat_open_id })
  195. toast('删除成功')
  196. refresh()
  197. } catch (e) {
  198. toast(e.msg || '删除失败')
  199. }
  200. }
  201. })
  202. }
  203. </script>
  204. <style lang="scss" scoped>
  205. .page,
  206. .scroll {
  207. height: 100vh;
  208. background: #f4f7fb;
  209. }
  210. .list {
  211. padding: 26rpx;
  212. }
  213. .item {
  214. padding: 28rpx 30rpx;
  215. border-radius: 20rpx;
  216. background: #fff;
  217. margin-bottom: 22rpx;
  218. box-shadow: 0 8rpx 24rpx rgba(35, 55, 90, 0.05);
  219. }
  220. .item-head {
  221. display: flex;
  222. align-items: center;
  223. min-width: 0;
  224. }
  225. .title {
  226. flex: 1;
  227. min-width: 0;
  228. color: #172033;
  229. font-size: 30rpx;
  230. font-weight: 700;
  231. line-height: 1.4;
  232. white-space: nowrap;
  233. overflow: hidden;
  234. text-overflow: ellipsis;
  235. }
  236. .status {
  237. margin-left: 18rpx;
  238. height: 40rpx;
  239. padding: 0 16rpx;
  240. border-radius: 22rpx;
  241. color: #0b8cff;
  242. font-size: 22rpx;
  243. line-height: 40rpx;
  244. background: #eef6ff;
  245. &.pending {
  246. color: #ff8a00;
  247. background: #fff5e9;
  248. }
  249. &.failed {
  250. color: #f04438;
  251. background: #fff1f1;
  252. }
  253. }
  254. .name-row {
  255. margin-top: 18rpx;
  256. display: flex;
  257. flex-wrap: wrap;
  258. gap: 12rpx;
  259. }
  260. .name-chip {
  261. max-width: 190rpx;
  262. height: 46rpx;
  263. padding: 0 18rpx;
  264. border-radius: 24rpx;
  265. color: #0b8cff;
  266. font-size: 25rpx;
  267. font-weight: 700;
  268. line-height: 46rpx;
  269. background: #f0f8ff;
  270. overflow: hidden;
  271. text-overflow: ellipsis;
  272. white-space: nowrap;
  273. }
  274. .desc {
  275. margin-top: 16rpx;
  276. color: #7f8896;
  277. font-size: 26rpx;
  278. line-height: 1.5;
  279. overflow: hidden;
  280. display: -webkit-box;
  281. -webkit-line-clamp: 3;
  282. -webkit-box-orient: vertical;
  283. }
  284. .meta {
  285. margin-top: 20rpx;
  286. display: flex;
  287. align-items: center;
  288. color: #a0aabb;
  289. font-size: 23rpx;
  290. }
  291. .del {
  292. margin-left: auto;
  293. color: #f04438;
  294. padding-left: 28rpx;
  295. }
  296. .empty,
  297. .loading {
  298. padding: 80rpx 0;
  299. color: #7f8896;
  300. font-size: 26rpx;
  301. text-align: center;
  302. }
  303. </style>