新增: 小程序端跨端AI对话
This commit is contained in:
36
apps/df-miniapp/src/components/MdView/MdView.vue
Normal file
36
apps/df-miniapp/src/components/MdView/MdView.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<!-- rich-text 小程序原生组件(无子组件依赖,稳)。marked md→html → rich-text :nodes 渲染。
|
||||
alpha 根因已查实(uni-app 2026 alpha 致 tap 失效),rich-text 非元凶,降级后正常用。 -->
|
||||
<rich-text :nodes="html" :selectable="true" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import { styleMarkdown } from '@/utils/mdRenderer'
|
||||
|
||||
defineOptions({ name: 'MdView' })
|
||||
|
||||
/**
|
||||
* Markdown 渲染气泡(miniapp,rich-text 原生方案)。
|
||||
*
|
||||
* 链路:marked.parse(md) → styleMarkdown 注入 inline style → rich-text :nodes(原生渲染 html 节点)。
|
||||
* - breaks:true 聊天换行(单换行转 <br>)
|
||||
* - P1-G:styleMarkdown 给代码块/链接/表格加 inline style(rich-text 不认 class)
|
||||
* - parse 失败回退原文
|
||||
* - 空内容返空(rich-text 不渲染)
|
||||
*
|
||||
* 流式:currentText 变化 → html computed 重算 → rich-text 重渲染。
|
||||
*/
|
||||
const props = defineProps<{ content: string }>()
|
||||
|
||||
const html = computed(() => {
|
||||
const src = props.content || ''
|
||||
if (!src) return ''
|
||||
try {
|
||||
return styleMarkdown(marked.parse(src, { breaks: true, async: false }) as string)
|
||||
} catch {
|
||||
return src
|
||||
}
|
||||
})
|
||||
</script>
|
||||
133
apps/df-miniapp/src/components/MentionInput/MentionInput.vue
Normal file
133
apps/df-miniapp/src/components/MentionInput/MentionInput.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<!-- @/ 联想弹层(MVP)。
|
||||
固定占位列表:不接真实技能路由/实体检索,仅作 chip 插入。
|
||||
/file /web /task /memory + @file @web @task 四类技能占位。
|
||||
渲染对齐 chat/index.vue 的 mp-weixin 稳定风格(原生 view/text + 绝对定位浮层)。 -->
|
||||
<view v-if="visible" class="mention-mask" @tap="onClose">
|
||||
<view class="mention-pop" @tap.stop>
|
||||
<view class="mention-head">
|
||||
<text class="mention-title">{{ trigger === '/' ? '技能' : '提及' }}</text>
|
||||
<text class="mention-hint">MVP 占位 · 点击插入</text>
|
||||
</view>
|
||||
<scroll-view class="mention-list" scroll-y>
|
||||
<view
|
||||
v-for="(item, idx) in items"
|
||||
:key="idx"
|
||||
class="mention-item"
|
||||
@tap="onSelect(item)"
|
||||
>
|
||||
<text class="mention-item-label">{{ item.label }}</text>
|
||||
<text class="mention-item-insert">{{ item.insert.trim() }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* MentionInput 联想弹层组件(MVP)。
|
||||
*
|
||||
* 设计:
|
||||
* - 纯展示:接收 visible/trigger/items,选中后 emit select(insert) + close
|
||||
* - 不查后端/不调路由:items 由父组件固定注入
|
||||
* - 触发字符 `/`(技能)或 `@`(提及)由父组件依据光标位置判定
|
||||
* - 选中即在 inputText 末尾插入对应字面量(如 `/file `),发送时整体纯文本走 ai.send
|
||||
*
|
||||
* props:
|
||||
* - visible:是否显示
|
||||
* - trigger:触发类型(`/` 技能 / `@` 提及)
|
||||
* - items:占位项列表 {label, insert}
|
||||
* emits:
|
||||
* - select(insert):插入文本
|
||||
* - close():关闭弹层
|
||||
*/
|
||||
defineOptions({ name: 'MentionInput' })
|
||||
|
||||
interface MentionItem {
|
||||
label: string
|
||||
insert: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
visible: boolean
|
||||
trigger: '/' | '@'
|
||||
items: MentionItem[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', insert: string): void
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
function onSelect(item: MentionItem): void {
|
||||
emit('select', item.insert)
|
||||
}
|
||||
|
||||
function onClose(): void {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mention-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
z-index: 999;
|
||||
}
|
||||
.mention-pop {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 56px;
|
||||
max-height: 260px;
|
||||
background-color: #1f1f1f;
|
||||
border: 1px solid #333333;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mention-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background-color: #262626;
|
||||
border-bottom: 1px solid #2e2e2e;
|
||||
}
|
||||
.mention-title {
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.mention-hint {
|
||||
color: #777777;
|
||||
font-size: 11px;
|
||||
}
|
||||
.mention-list {
|
||||
max-height: 220px;
|
||||
}
|
||||
.mention-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
}
|
||||
.mention-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.mention-item-label {
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.mention-item-insert {
|
||||
color: #4a9eff;
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user