|
- <template>
- <view class="page">
- <scroll-view
- class="scroll"
- scroll-y
- refresher-enabled
- :refresher-triggered="refreshing"
- @refresherrefresh="refresh"
- @scrolltolower="loadMore"
- >
- <view class="list">
- <view v-for="item in list" :key="item.chat_open_id" class="item" @click="openItem(item)">
- <view class="item-head">
- <view class="title">{{ item.question_content || 'AI取名' }}</view>
- <text class="status" :class="{ pending: Number(item.is_suc) === 0, failed: Number(item.is_suc) < 0 }">
- {{ getStatusText(item) }}
- </text>
- </view>
-
- <view v-if="getNames(item).length" class="name-row">
- <text v-for="name in getNames(item)" :key="name" class="name-chip">{{ name }}</text>
- </view>
-
- <view class="desc">{{ getSummary(item) }}</view>
-
- <view class="meta">
- <text>{{ formatTime(item.add_time) }}</text>
- <text class="del" @click.stop="deleteItem(item)">删除</text>
- </view>
- </view>
- </view>
-
- <view v-if="!list.length && !loading" class="empty">暂无历史记录</view>
- <view v-if="loading" class="loading">加载中...</view>
- </scroll-view>
- </view>
- </template>
-
- <script setup>
- import { ref } from 'vue'
- import { onLoad, onUnload } from '@dcloudio/uni-app'
- import dayjs from 'dayjs'
- import { toolApi } from '@/api/index.js'
- import { useApp } from '@/utils/useApp.js'
-
- const { toast } = useApp()
- const robotId = ref('')
- const list = ref([])
- const page = ref(1)
- const total = ref(0)
- const loading = ref(false)
- const refreshing = ref(false)
- let timer = 0
-
- onLoad((options) => {
- robotId.value = options.robot_id || ''
- refresh()
- })
-
- onUnload(() => clearTimeout(timer))
-
- function formatTime(value) {
- if (!value) return ''
- return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm')
- }
-
- async function fetchList(reset = false) {
- if (loading.value) return
- loading.value = true
- try {
- const res = await toolApi.getNameList({
- page_no: page.value,
- page_size: 10,
- robot_open_id: robotId.value,
- role: 2
- })
- const rows = res.list || []
- total.value = Number((res.data && res.data.total) || rows.length)
- list.value = reset ? rows : list.value.concat(rows)
- scheduleRefresh()
- } catch (e) {
- toast(e.msg || '历史记录加载失败')
- } finally {
- loading.value = false
- refreshing.value = false
- }
- }
-
- function scheduleRefresh() {
- clearTimeout(timer)
- if (list.value.some((item) => Number(item.is_suc) === 0)) {
- timer = setTimeout(() => refreshSilent(), 2500)
- }
- }
-
- function refresh() {
- refreshing.value = true
- refreshSilent()
- }
-
- function refreshSilent() {
- page.value = 1
- fetchList(true)
- }
-
- function loadMore() {
- if (loading.value || list.value.length >= total.value) return
- page.value += 1
- fetchList()
- }
-
- function openItem(item) {
- uni.navigateTo({
- url: `/pages/tool/name-result?id=${item.chat_open_id}&robot_id=${robotId.value}&key=naming`
- })
- }
-
- function getStatusText(item) {
- if (Number(item.is_suc) === 0) return '生成中'
- if (Number(item.is_suc) < 0) return '失败'
- return '完成'
- }
-
- function getParsed(item) {
- if (item._parsed) return item._parsed
- item._parsed = parseResult(item.content)
- return item._parsed
- }
-
- function getNames(item) {
- const parsed = getParsed(item)
- return (parsed.names || []).map((name) => name.name).filter(Boolean).slice(0, 3)
- }
-
- function getSummary(item) {
- if (Number(item.is_suc) === 0) return '正在生成取名方案,请稍后查看'
- if (Number(item.is_suc) < 0) return item.content || '生成失败'
-
- const parsed = getParsed(item)
- if (parsed.summary) return parsed.summary
-
- const first = parsed.names && parsed.names[0]
- if (first) return [first.meaning, first.reason].filter(Boolean).join(' ')
- return '点击查看取名结果'
- }
-
- function parseResult(content) {
- if (!content) return { summary: '', names: [] }
- let text = String(content).trim()
- text = text.replace(/^```json\s*/i, '').replace(/^```\s*/i, '').replace(/```$/i, '').trim()
-
- const lineResult = parseJsonLines(text)
- if (lineResult.names.length) return lineResult
-
- const markedResult = parseMarkedText(text)
- if (markedResult.names.length) return markedResult
-
- const start = text.indexOf('{')
- const end = text.lastIndexOf('}')
- if (start >= 0 && end > start) text = text.substring(start, end + 1)
-
- try {
- const data = JSON.parse(text)
- return {
- summary: data.summary || '',
- names: Array.isArray(data.names) ? data.names : []
- }
- } catch (e) {
- return { summary: '', names: [] }
- }
- }
-
- function parseMarkedText(text) {
- const data = { summary: '', names: [] }
- const value = String(text || '')
- const summaryMatch = value.match(/整体建议[::]([\s\S]*?)(?=\n\s*【|$)/)
- if (summaryMatch) data.summary = summaryMatch[1].trim()
-
- const reg = /【([^】]+)】([\s\S]*?)(?=\n\s*【|$)/g
- let match
- while ((match = reg.exec(value))) {
- const body = match[2] || ''
- data.names.push({
- name: match[1].trim(),
- pinyin: matchField(body, '拼音'),
- score: parseInt(matchField(body, '评分')) || 0,
- source: matchField(body, '出处'),
- meaning: matchField(body, '寓意'),
- tone: matchField(body, '音律'),
- reason: matchField(body, '推荐')
- })
- }
- data.names = data.names.filter((item) => item.name).slice(0, 5)
- return data
- }
-
- function matchField(body, label) {
- const reg = new RegExp(`${label}[::]([^\\n\\r]*)`)
- const match = String(body || '').match(reg)
- return match ? match[1].trim() : ''
- }
-
- function parseJsonLines(text) {
- const data = { summary: '', names: [] }
- String(text || '').split(/\r?\n/).forEach((line) => {
- line = line.trim()
- if (!line) return
- try {
- const item = JSON.parse(line)
- if (item.type === 'summary') data.summary = item.content || ''
- if (item.type === 'name' && item.data) data.names.push(item.data)
- } catch (e) {}
- })
- return data
- }
-
- function deleteItem(item) {
- uni.showModal({
- title: '温馨提示',
- content: '是否确认删除该内容?',
- confirmText: '确认删除',
- success: async (res) => {
- if (!res.confirm) return
- try {
- await toolApi.deleteName({ chat_open_id: item.chat_open_id })
- toast('删除成功')
- refresh()
- } catch (e) {
- toast(e.msg || '删除失败')
- }
- }
- })
- }
- </script>
-
- <style lang="scss" scoped>
- .page,
- .scroll {
- height: 100vh;
- background: #f4f7fb;
- }
-
- .list {
- padding: 26rpx;
- }
-
- .item {
- padding: 28rpx 30rpx;
- border-radius: 20rpx;
- background: #fff;
- margin-bottom: 22rpx;
- box-shadow: 0 8rpx 24rpx rgba(35, 55, 90, 0.05);
- }
-
- .item-head {
- display: flex;
- align-items: center;
- min-width: 0;
- }
-
- .title {
- flex: 1;
- min-width: 0;
- color: #172033;
- font-size: 30rpx;
- font-weight: 700;
- line-height: 1.4;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .status {
- margin-left: 18rpx;
- height: 40rpx;
- padding: 0 16rpx;
- border-radius: 22rpx;
- color: #0b8cff;
- font-size: 22rpx;
- line-height: 40rpx;
- background: #eef6ff;
-
- &.pending {
- color: #ff8a00;
- background: #fff5e9;
- }
-
- &.failed {
- color: #f04438;
- background: #fff1f1;
- }
- }
-
- .name-row {
- margin-top: 18rpx;
- display: flex;
- flex-wrap: wrap;
- gap: 12rpx;
- }
-
- .name-chip {
- max-width: 190rpx;
- height: 46rpx;
- padding: 0 18rpx;
- border-radius: 24rpx;
- color: #0b8cff;
- font-size: 25rpx;
- font-weight: 700;
- line-height: 46rpx;
- background: #f0f8ff;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .desc {
- margin-top: 16rpx;
- color: #7f8896;
- font-size: 26rpx;
- line-height: 1.5;
- overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- }
-
- .meta {
- margin-top: 20rpx;
- display: flex;
- align-items: center;
- color: #a0aabb;
- font-size: 23rpx;
- }
-
- .del {
- margin-left: auto;
- color: #f04438;
- padding-left: 28rpx;
- }
-
- .empty,
- .loading {
- padding: 80rpx 0;
- color: #7f8896;
- font-size: 26rpx;
- text-align: center;
- }
- </style>
|