| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- <template>
- <view :key="layoutKey" :class="pageRootClass" class="tab-page ai-page">
- <view class="ai-list-shell">
- <up-virtual-list
- ref="aiVList"
- class="ai-vlist"
- :list-data="historyList"
- :item-height="slotHeightPx"
- :height="scrollAreaPx"
- :buffer="8"
- key-field="id"
- :scroll-top="vScrollTop"
- @update:scrollTop="vScrollTop = $event"
- >
- <template #default="{ item }">
- <view class="ai-row-cell">
- <view
- class="ai-row"
- :class="{ 'ai-row--hl': highlightId === item.id }"
- role="button"
- :style="{ height: rowBodyPx + 'px' }"
- @click="openSession(item)"
- @longpress.stop="onRowLongPress(item)"
- >
- <view class="ai-row__left">
- <view class="ai-row__icon-circle">
- <up-icon name="chat" color="#22C55E" :size="22" />
- </view>
- </view>
- <view class="ai-row__right">
- <text class="ai-row__title">{{ item.title }}</text>
- <text class="ai-row__hint">{{ item.preview }}</text>
- </view>
- </view>
- </view>
- </template>
- </up-virtual-list>
- </view>
- <view class="ai-float">
- <view class="ai-float__inner">
- <up-button
- type="primary"
- shape="circle"
- :text="$t('aiPage.createChat')"
- :custom-style="floatBtnStyle"
- @click="createSession"
- />
- </view>
- </view>
- </view>
- </template>
- <script>
- import UButton from 'uview-plus/components/u-button/u-button.vue'
- import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
- import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
- import tabPage from '@/mixins/tabPage'
- import { ensureApiToken } from '@/utils/apiAuth'
- import {
- resolveSessionId,
- normalizeSessionRow,
- sessionTitle,
- DEFAULT_SESSION_TITLE
- } from '@/utils/aiConsult'
- import {
- listAiConsultSessions,
- createAiConsultSession,
- hideAiConsultSession
- } from '@/api/diseaseTreatment/aiOnlineConsult'
- const ASSISTANT_PATH = '/package-a/ai-assistant/index'
- /** 单行卡片内容区高度(rpx),不含与下一行的间距 */
- const ROW_BODY_RPX = 168
- /** 列表项之间的间距(rpx),计入虚拟列表单项高度 */
- const ROW_GAP_RPX = 20
- export default {
- components: {
- 'up-button': UButton,
- 'up-icon': UIcon,
- 'up-virtual-list': UVirtualList
- },
- mixins: [tabPage],
- data() {
- return {
- navTitleKey: 'nav.ai',
- sessions: [],
- listLoading: false,
- rowBodyPx: 76,
- marginPx: 10,
- slotHeightPx: 86,
- scrollAreaPx: 400,
- vScrollTop: 0,
- highlightId: '',
- suppressRowClick: false,
- floatBtnStyle:
- 'min-width:280rpx;max-width:92%;height:auto;padding:22rpx 40rpx;display:flex;align-items:center;justify-content:center;'
- }
- },
- computed: {
- historyList() {
- return this.sessions.map((s, idx) => {
- const id = resolveSessionId(s)
- const title =
- sessionTitle(s, this.$t('aiOnlineConsult.defaultSessionTitle')) ||
- this.$t('aiPage.sessionTitle', { n: idx + 1 })
- const preview = (s.lastMessagePreview || '').trim() || this.$t('aiPage.openChat')
- return { id, n: idx + 1, title, preview, raw: s }
- })
- }
- },
- watch: {
- historyList() {
- this.$nextTick(() => {
- this.$refs.aiVList?.measureContainerHeight?.()
- })
- }
- },
- onShow() {
- if (ensureApiToken(false)) {
- this.loadSessions()
- }
- },
- onReady() {
- try {
- this.marginPx = Math.ceil(uni.upx2px(ROW_GAP_RPX))
- this.rowBodyPx = Math.max(64, Math.ceil(uni.upx2px(ROW_BODY_RPX)))
- this.slotHeightPx = this.rowBodyPx + this.marginPx
- } catch (e) {
- this.marginPx = 10
- this.rowBodyPx = 76
- this.slotHeightPx = 86
- }
- this.applyScrollLayoutFallback()
- this.$nextTick(() => this.measureListShell())
- },
- methods: {
- applyScrollLayoutFallback() {
- const sys = uni.getSystemInfoSync()
- const winH = sys.windowHeight || 600
- this.scrollAreaPx = Math.max(200, winH - 30)
- },
- measureListShell() {
- uni.createSelectorQuery()
- .in(this)
- .select('.ai-list-shell')
- .boundingClientRect((rect) => {
- if (rect && rect.height > 0) {
- this.scrollAreaPx = Math.max(200, Math.floor(rect.height))
- this.$nextTick(() => {
- this.$refs.aiVList?.measureContainerHeight?.()
- })
- }
- })
- .exec()
- },
- loadSessions() {
- if (!ensureApiToken()) return
- this.listLoading = true
- listAiConsultSessions({
- pageNum: 1,
- pageSize: 100
- })
- .then((res) => {
- this.sessions = (res.rows || []).map((row) => normalizeSessionRow(row))
- })
- .catch(() => {
- this.sessions = []
- })
- .finally(() => {
- this.listLoading = false
- })
- },
- openSession(item) {
- if (this.suppressRowClick || !item || !item.id) return
- let url = `${ASSISTANT_PATH}?sessionId=${encodeURIComponent(item.id)}`
- const llm = item.raw && (item.raw.llmSessionId || null)
- if (llm) {
- url += `&llmSessionId=${encodeURIComponent(llm)}`
- }
- uni.navigateTo({ url })
- },
- onRowLongPress(item) {
- this.suppressRowClick = true
- setTimeout(() => {
- this.suppressRowClick = false
- }, 450)
- this.highlightId = item.id
- uni.showActionSheet({
- itemList: [this.$t('messagePage.delete')],
- success: (res) => {
- if (res.tapIndex === 0) {
- this.removeSession(item.id)
- }
- this.highlightId = ''
- },
- fail: () => {
- this.highlightId = ''
- }
- })
- },
- removeSession(id) {
- if (!id || !ensureApiToken()) return
- hideAiConsultSession(id)
- .then(() => {
- this.sessions = this.sessions.filter((s) => resolveSessionId(s) !== id)
- this.highlightId = ''
- this.$nextTick(() => {
- this.$refs.aiVList?.measureContainerHeight?.()
- })
- })
- .catch(() => {})
- },
- createSession() {
- if (!ensureApiToken()) return
- uni.showLoading({ mask: true })
- createAiConsultSession({})
- .then((res) => {
- const data = res.data || {}
- const id = resolveSessionId(data)
- if (!id) return
- const row = normalizeSessionRow({
- ...data,
- id,
- realSessionId: id,
- llmSessionId: null
- })
- const exists = this.sessions.some((s) => resolveSessionId(s) === id)
- if (!exists) {
- this.sessions.unshift(row)
- }
- uni.navigateTo({
- url: `${ASSISTANT_PATH}?sessionId=${encodeURIComponent(id)}&new=1`
- })
- })
- .finally(() => {
- uni.hideLoading()
- })
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- @import '@/styles/morandi.scss';
- @import '@/styles/tab-page.scss';
- .ai-page {
- display: flex;
- flex-direction: column;
- min-width: 0;
- min-height: 100%;
- padding: 24rpx 16rpx 0;
- box-sizing: border-box;
- gap: 16rpx;
- background: $morandi-bg-page;
- }
- .ai-list-shell {
- flex: 1;
- min-height: 0;
- height: 0;
- min-width: 0;
- box-sizing: border-box;
- }
- .ai-vlist {
- width: 100%;
- }
- .ai-row-cell {
- height: 100%;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- }
- .ai-row {
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- min-width: 0;
- overflow: hidden;
- padding: 22rpx 20rpx;
- gap: 20rpx;
- box-sizing: border-box;
- border-radius: 16rpx;
- background: $morandi-bg-card;
- border: 1rpx solid $morandi-border;
- flex-shrink: 0;
- }
- .ai-row--hl {
- background: $morandi-highlight;
- border-color: $morandi-highlight-border;
- }
- .ai-row__left {
- flex-shrink: 0;
- padding-top: 4rpx;
- }
- .ai-row__icon-circle {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 88rpx;
- height: 88rpx;
- border-radius: 50%;
- background: rgba(34, 197, 94, 0.12);
- }
- .ai-row__right {
- display: flex;
- flex: 1;
- min-width: 0;
- width: 0;
- flex-direction: column;
- justify-content: center;
- gap: 8rpx;
- overflow: hidden;
- }
- .ai-row__title {
- display: block;
- width: 100%;
- max-width: 100%;
- min-width: 0;
- font-size: 32rpx;
- line-height: 1.35;
- font-weight: 600;
- color: $morandi-text;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- word-break: normal;
- overflow-wrap: normal;
- flex-shrink: 0;
- }
- .ai-row__hint {
- display: block;
- width: 100%;
- max-width: 100%;
- min-width: 0;
- font-size: 24rpx;
- line-height: 1.35;
- color: $morandi-text-muted;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- word-break: normal;
- overflow-wrap: normal;
- flex-shrink: 1;
- }
- .ai-float {
- position: fixed;
- left: 0;
- right: 0;
- z-index: 200;
- display: flex;
- justify-content: center;
- padding: 0 24rpx;
- bottom: calc(140rpx + env(safe-area-inset-bottom));
- pointer-events: none;
- }
- .ai-float__inner {
- pointer-events: auto;
- }
- </style>
|