feat: 小程序统一走 Go 后台,咨询与登录切到 /api/v1
去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<yxg-page title="AI问答">
|
||||
<text v-if="bootLoading" class="hint">加载中…</text>
|
||||
<text v-else-if="bootError" class="err">{{ bootError }}</text>
|
||||
<template v-else>
|
||||
<view class="quota">
|
||||
<text>今日剩余 {{ quota?.left ?? quota?.remaining ?? '—' }} 次</text>
|
||||
<text class="link" @click="yxgGo('/membership')">加购 ›</text>
|
||||
</view>
|
||||
<scroll-view v-if="profiles.length > 1" class="chips" scroll-x>
|
||||
<view
|
||||
v-for="p in profiles"
|
||||
:key="p.id"
|
||||
class="chip"
|
||||
:class="{ on: profileId === p.id }"
|
||||
@click="profileId = p.id"
|
||||
>
|
||||
<text>{{ p.display_name || (p.relation === 'self' ? '我' : 'TA') }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="!messages.length" class="scenes">
|
||||
<view v-for="s in scenes" :key="s.key" class="scene" @click="useScene(s)">
|
||||
<text>{{ s.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="msgs">
|
||||
<view v-for="m in messages" :key="m.id" class="msg" :class="m.role">
|
||||
<text>{{ m.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="sendError" class="err">{{ sendError }}</text>
|
||||
<view class="composer">
|
||||
<input class="inp" v-model="draft" placeholder="结合档案聊聊卡住的事…" confirm-type="send" @confirm="send" />
|
||||
<view class="send" :class="{ off: sending || !draft.trim() }" @click="send"><text>发送</text></view>
|
||||
</view>
|
||||
</template>
|
||||
</yxg-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import YxgPage from '@/components/yxg/yxg-page.vue'
|
||||
import { yxgApi } from '@/util/yxgApi.js'
|
||||
import { ensureAccount, pickableArchiveList } from '@/util/yxgAuth.js'
|
||||
import { yxgGo } from '@/util/yxgNav.js'
|
||||
|
||||
const scenes = [
|
||||
{ key: 'self', label: '我想更了解自己的性格与互动风格', prompt: '我想更了解自己的性格与互动风格。' },
|
||||
{ key: 'relation', label: '和重要的人相处时该如何沟通?', prompt: '和重要的人相处时,我该如何更好地沟通?' },
|
||||
{ key: 'emotion', label: '最近情绪有点乱,想梳理一下', prompt: '最近情绪有点乱,我想梳理一下。' },
|
||||
{ key: 'career', label: '职业选择上可以注意什么?', prompt: '从我的特点看,职业选择上可以注意什么?' },
|
||||
]
|
||||
|
||||
const bootLoading = ref(true)
|
||||
const bootError = ref('')
|
||||
const profiles = ref([])
|
||||
const profileId = ref('')
|
||||
const threadId = ref('')
|
||||
const messages = ref([])
|
||||
const draft = ref('')
|
||||
const sending = ref(false)
|
||||
const sendError = ref('')
|
||||
const quota = ref(null)
|
||||
|
||||
async function boot() {
|
||||
bootLoading.value = true
|
||||
bootError.value = ''
|
||||
if (!(await ensureAccount('/ask'))) {
|
||||
bootLoading.value = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
const [list, q] = await Promise.all([yxgApi.listProfiles(), yxgApi.getAskQuota()])
|
||||
profiles.value = pickableArchiveList(list.items || [])
|
||||
quota.value = q
|
||||
const self = profiles.value.find((p) => p.relation === 'self')
|
||||
profileId.value = self?.id || profiles.value[0]?.id || ''
|
||||
} catch (e) {
|
||||
bootError.value = e instanceof Error ? e.message : '加载失败'
|
||||
} finally {
|
||||
bootLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureThread() {
|
||||
if (threadId.value) return threadId.value
|
||||
if (!profileId.value) {
|
||||
sendError.value = '请先完善档案'
|
||||
yxgGo('/profile')
|
||||
throw new Error('no profile')
|
||||
}
|
||||
const th = await yxgApi.createAskThread({ profile_id: profileId.value })
|
||||
threadId.value = th.id || th.thread_id
|
||||
return threadId.value
|
||||
}
|
||||
|
||||
async function send() {
|
||||
const text = draft.value.trim()
|
||||
if (!text || sending.value) return
|
||||
sending.value = true
|
||||
sendError.value = ''
|
||||
try {
|
||||
const id = await ensureThread()
|
||||
const out = await yxgApi.sendAskMessage(id, text)
|
||||
draft.value = ''
|
||||
if (out.user_message) messages.value.push(out.user_message)
|
||||
if (out.assistant_message) messages.value.push(out.assistant_message)
|
||||
if (out.quota) quota.value = out.quota
|
||||
} catch (e) {
|
||||
sendError.value = e instanceof Error ? e.message : '发送失败'
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function useScene(s) {
|
||||
draft.value = s.prompt
|
||||
send()
|
||||
}
|
||||
|
||||
onShow(boot)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page { background-color: #ffd1c7; }
|
||||
</style>
|
||||
<style scoped>
|
||||
.hint, .err { display: block; padding: 16px; font-size: 13px; color: #999; }
|
||||
.err { color: #e54d42; }
|
||||
.quota {
|
||||
margin: 8px 16px; padding: 10px 12px; background: #fff; border-radius: 12px;
|
||||
display: flex; justify-content: space-between; font-size: 13px; color: #666;
|
||||
}
|
||||
.link { color: #e54d42; }
|
||||
.chips { white-space: nowrap; padding: 0 16px 8px; }
|
||||
.chip {
|
||||
display: inline-block; margin-right: 8px; padding: 6px 12px; border-radius: 999px;
|
||||
background: #fff; font-size: 13px; color: #666;
|
||||
}
|
||||
.chip.on { background: #ffe4e4; color: #e54d42; }
|
||||
.scenes { padding: 0 16px; }
|
||||
.scene {
|
||||
background: #fff; border-radius: 14px; padding: 12px 14px; margin-bottom: 8px; font-size: 14px; color: #333;
|
||||
}
|
||||
.msgs { padding: 8px 16px 80px; }
|
||||
.msg { padding: 10px 12px; border-radius: 14px; margin-bottom: 8px; font-size: 14px; line-height: 1.55; max-width: 88%; }
|
||||
.msg.user, .msg.human { margin-left: auto; background: #e54d42; color: #fff; }
|
||||
.msg.assistant, .msg.ai { background: #fff; color: #333; }
|
||||
.composer {
|
||||
position: fixed; left: 0; right: 0; bottom: 0; padding: 10px 12px 24px;
|
||||
background: #fff9f7; display: flex; gap: 8px; align-items: center;
|
||||
}
|
||||
.inp { flex: 1; height: 40px; background: #fff; border-radius: 20px; padding: 0 14px; font-size: 14px; }
|
||||
.send { padding: 0 14px; height: 40px; border-radius: 20px; background: #e54d42; color: #fff; display: flex; align-items: center; font-size: 14px; }
|
||||
.send.off { opacity: 0.45; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user