Add Vue user H5 client
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
defineProps<{
|
||||
loading: boolean;
|
||||
disabled: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
send: [message: string];
|
||||
stop: [];
|
||||
}>();
|
||||
|
||||
const input = ref("");
|
||||
|
||||
function send() {
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
emit("send", text);
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
send();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="composer" @submit.prevent="send">
|
||||
<textarea
|
||||
v-model="input"
|
||||
rows="1"
|
||||
placeholder="请输入你的问题"
|
||||
:disabled="disabled"
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<button v-if="loading" type="button" class="stop-btn" @click="emit('stop')">停止</button>
|
||||
<button v-else type="submit" class="send-btn" :disabled="disabled || !input.trim()">发送</button>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
streaming?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="message" :class="role">
|
||||
<div class="avatar">{{ role === "assistant" ? "AI" : "我" }}</div>
|
||||
<div class="bubble">
|
||||
<div class="content">{{ content }}</div>
|
||||
<span v-if="streaming" class="typing">正在生成</span>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { api, saveToken } from "../services/api";
|
||||
import type { UserProfile } from "../types/api";
|
||||
|
||||
const emit = defineEmits<{
|
||||
loggedIn: [user: UserProfile];
|
||||
}>();
|
||||
|
||||
const phone = ref("");
|
||||
const code = ref("123456");
|
||||
const loading = ref(false);
|
||||
const message = ref("开发阶段验证码默认 123456");
|
||||
|
||||
async function sendCode() {
|
||||
loading.value = true;
|
||||
message.value = "";
|
||||
try {
|
||||
await api.sendSms(phone.value);
|
||||
message.value = "验证码已发送,开发阶段默认 123456";
|
||||
} catch (error) {
|
||||
message.value = error instanceof Error ? error.message : "发送失败";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
loading.value = true;
|
||||
message.value = "";
|
||||
try {
|
||||
const result = await api.login(phone.value, code.value);
|
||||
saveToken(result.token);
|
||||
emit("loggedIn", result.user);
|
||||
} catch (error) {
|
||||
message.value = error instanceof Error ? error.message : "登录失败";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="login-panel">
|
||||
<div class="brand-mark">AI</div>
|
||||
<h1>AI知识库助手</h1>
|
||||
<p>企业知识统一问答入口</p>
|
||||
|
||||
<label>
|
||||
手机号
|
||||
<input v-model="phone" inputmode="numeric" maxlength="11" placeholder="请输入手机号" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
验证码
|
||||
<div class="code-row">
|
||||
<input v-model="code" inputmode="numeric" maxlength="8" placeholder="验证码" />
|
||||
<button type="button" class="secondary" :disabled="loading || phone.length < 11" @click="sendCode">
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<button type="button" class="primary" :disabled="loading || phone.length < 11 || code.length < 4" @click="login">
|
||||
{{ loading ? "处理中..." : "立即进入" }}
|
||||
</button>
|
||||
<div class="form-tip">{{ message }}</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChatSession } from "../types/api";
|
||||
|
||||
defineProps<{
|
||||
open: boolean;
|
||||
sessions: ChatSession[];
|
||||
activeSessionId: number | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
select: [sessionId: number];
|
||||
create: [];
|
||||
delete: [sessionId: number];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="drawer-mask" :class="{ show: open }" @click="emit('close')"></div>
|
||||
<aside class="session-drawer" :class="{ open }">
|
||||
<div class="drawer-head">
|
||||
<strong>历史会话</strong>
|
||||
<button type="button" class="icon-btn" @click="emit('close')">×</button>
|
||||
</div>
|
||||
<button type="button" class="new-session-btn" @click="emit('create')">+ 新聊天</button>
|
||||
<div class="session-list">
|
||||
<button
|
||||
v-for="session in sessions"
|
||||
:key="session.id"
|
||||
type="button"
|
||||
class="session-item"
|
||||
:class="{ active: session.id === activeSessionId }"
|
||||
@click="emit('select', session.id)"
|
||||
>
|
||||
<span>{{ session.title }}</span>
|
||||
<small>{{ session.messageCount }} 条消息</small>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
Reference in New Issue
Block a user