feat: initial Vue3 SDK Demo application

- Login page with quick user selection
- Real-time chat with WebSocket (STOMP)
- Conversation/friend/group list sidebar
- API test console with operation logs
- Integration with local dev environment
这个提交包含在:
徐勤民 2026-04-30 16:59:06 +08:00
当前提交 28c1110344
共有 9 个文件被更改,包括 932 次插入0 次删除

4
.gitignore vendored 普通文件
查看文件

@ -0,0 +1,4 @@
node_modules
dist
.DS_Store
*.log

12
index.html 普通文件
查看文件

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3 SDK Demo</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

22
package.json 普通文件
查看文件

@ -0,0 +1,22 @@
{
"name": "xuqmgroup-vue3-sdk-demo",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.13",
"element-plus": "^2.9.1",
"@element-plus/icons-vue": "^2.3.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"typescript": "^5.8.2",
"vite": "^6.2.2",
"vue-tsc": "^2.2.8"
}
}

40
src/App.vue 普通文件
查看文件

@ -0,0 +1,40 @@
<template>
<div class="app">
<LoginView v-if="!token" @login="handleLogin" />
<ChatDemoView v-else :token="token" :userId="userId" @logout="handleLogout" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { setToken, setUserId } from '@xuqm/vue3-sdk'
import LoginView from './views/LoginView.vue'
import ChatDemoView from './views/ChatDemoView.vue'
const token = ref<string>('')
const userId = ref<string>('')
function handleLogin(t: string, u: string) {
token.value = t
userId.value = u
setToken(t)
setUserId(u)
}
function handleLogout() {
token.value = ''
userId.value = ''
setToken(null)
setUserId(null)
}
</script>
<style>
html, body, #app, .app {
margin: 0;
padding: 0;
height: 100%;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', Arial, sans-serif;
}
</style>

17
src/main.ts 普通文件
查看文件

@ -0,0 +1,17 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import { init } from '@xuqm/vue3-sdk'
init({
appKey: 'ak_demo_chat',
appSecret: 'as_demo_secret',
debug: true,
baseUrl: 'http://192.168.113.37:8082',
wsUrl: 'ws://192.168.113.37:8082/ws/im',
})
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

699
src/views/ChatDemoView.vue 普通文件
查看文件

@ -0,0 +1,699 @@
<template>
<div class="chat-demo">
<!-- 顶部栏 -->
<div class="header">
<div class="header-left">
<el-tag :type="connected ? 'success' : 'danger'" effect="dark" round>
{{ connected ? 'WebSocket 已连接' : 'WebSocket 未连接' }}
</el-tag>
<span class="user-info">当前用户: <b>{{ userId }}</b></span>
</div>
<div class="header-right">
<el-button size="small" @click="testAllApis" :loading="testing">一键API测试</el-button>
<el-button size="small" type="danger" @click="$emit('logout')">退出登录</el-button>
</div>
</div>
<div class="main">
<!-- 左侧边栏 -->
<div class="sidebar">
<el-tabs v-model="activeTab" stretch>
<el-tab-pane label="会话" name="conversations">
<div class="list-container">
<div
v-for="conv in conversations"
:key="conv.targetId"
:class="['conv-item', { active: currentTarget?.targetId === conv.targetId }]"
@click="selectConversation(conv)"
>
<div class="conv-title">
<span>{{ conv.targetId }}</span>
<el-badge v-if="conv.unreadCount > 0" :value="conv.unreadCount" />
</div>
<div class="conv-meta">
<span class="conv-preview">{{ conv.lastMsgContent || '暂无消息' }}</span>
<span class="conv-time">{{ formatTime(conv.lastMsgTime) }}</span>
</div>
</div>
<el-empty v-if="conversations.length === 0" description="暂无会话" />
</div>
</el-tab-pane>
<el-tab-pane label="好友" name="friends">
<div class="list-container">
<el-button size="small" @click="loadFriends" :loading="loadingFriends">刷新好友</el-button>
<el-divider />
<div v-for="f in friends" :key="f" class="friend-item" @click="startChat(f, 'SINGLE')">
<el-icon><User /></el-icon> {{ f }}
</div>
<el-empty v-if="friends.length === 0" description="暂无好友" />
</div>
</el-tab-pane>
<el-tab-pane label="群组" name="groups">
<div class="list-container">
<el-button size="small" @click="loadGroups" :loading="loadingGroups">刷新群组</el-button>
<el-divider />
<div v-for="g in groups" :key="g.id" class="group-item" @click="startChat(g.id, 'GROUP')">
<el-icon><ChatDotRound /></el-icon> {{ g.name }}
</div>
<el-empty v-if="groups.length === 0" description="暂无群组" />
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 中间聊天区 -->
<div class="chat-area">
<div v-if="currentTarget" class="chat-header">
{{ currentTarget.targetId }} ({{ currentTarget.chatType === 'SINGLE' ? '单聊' : '群聊' }})
</div>
<div v-else class="chat-header">选择一个会话开始聊天</div>
<div ref="msgContainer" class="message-list">
<div
v-for="msg in messages"
:key="msg.id"
:class="['message-row', msg.fromId === userId ? 'self' : 'other']"
>
<div class="message-bubble">
<div class="msg-sender">{{ msg.fromId }}</div>
<div class="msg-content">
<span v-if="msg.revoked" class="revoked">消息已撤回</span>
<span v-else>{{ msg.content }}</span>
</div>
<div class="msg-meta">
<el-tag size="small" :type="statusType(msg.status)">{{ msg.status }}</el-tag>
<span class="msg-time">{{ formatTime(msg.createdAt) }}</span>
<el-button
v-if="msg.fromId === userId && !msg.revoked"
link
size="small"
type="danger"
@click="revokeMsg(msg.id)"
>
撤回
</el-button>
</div>
</div>
</div>
<el-empty v-if="messages.length === 0 && currentTarget" description="暂无消息" />
</div>
<div v-if="currentTarget" class="input-area">
<el-input
v-model="inputText"
type="textarea"
:rows="2"
placeholder="输入消息,按 Enter 发送"
@keydown.enter.prevent="sendText"
/>
<div class="input-actions">
<el-button @click="markConversationAsRead">标记已读</el-button>
<el-button type="primary" @click="sendText" :disabled="!inputText.trim()">发送</el-button>
</div>
</div>
</div>
<!-- 右侧测试面板 -->
<div class="test-panel">
<h4>API 测试控制台</h4>
<el-scrollbar height="calc(100vh - 140px)">
<el-collapse v-model="activeCollapse">
<el-collapse-item title="消息操作" name="msg">
<el-button size="small" @click="loadHistory" :loading="loading">加载历史</el-button>
<el-button size="small" @click="searchMsgs" :loading="loading">搜索消息</el-button>
</el-collapse-item>
<el-collapse-item title="好友操作" name="friend">
<el-input v-model="friendInput" size="small" placeholder="用户ID">
<template #append>
<el-button @click="addFriend" :loading="loading">加好友</el-button>
</template>
</el-input>
<el-button size="small" @click="loadFriendRequests" :loading="loading" style="margin-top:8px">
好友请求列表
</el-button>
</el-collapse-item>
<el-collapse-item title="群组操作" name="group">
<el-button size="small" @click="createGroup" :loading="loading">创建测试群组</el-button>
<el-button size="small" @click="joinGroup" :loading="loading" style="margin-top:8px">申请入群</el-button>
</el-collapse-item>
<el-collapse-item title="会话操作" name="conv">
<el-button size="small" @click="pinConv" :loading="loading">置顶会话</el-button>
<el-button size="small" @click="muteConv" :loading="loading" style="margin-top:8px">静音会话</el-button>
<el-button size="small" @click="deleteConv" :loading="loading" style="margin-top:8px">删除会话</el-button>
</el-collapse-item>
<el-collapse-item title="用户操作" name="user">
<el-button size="small" @click="getProfile" :loading="loading">获取资料</el-button>
<el-button size="small" @click="updateProfile" :loading="loading" style="margin-top:8px">更新昵称</el-button>
</el-collapse-item>
</el-collapse>
<el-divider />
<div class="log-area">
<div
v-for="(log, i) in logs"
:key="i"
:class="['log-item', log.type]"
>
<span class="log-time">{{ log.time }}</span>
<span class="log-text">{{ log.text }}</span>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { User, ChatDotRound } from '@element-plus/icons-vue'
import { useIm } from '@xuqm/vue3-sdk'
import type { ConversationView, ImGroup, FriendRequest } from '@xuqm/vue3-sdk'
import {
sendFriendRequest,
listFriendRequests,
acceptFriendRequest,
searchMessages,
getProfile as apiGetProfile,
updateProfile as apiUpdateProfile,
} from '@xuqm/vue3-sdk'
const props = defineProps<{ token: string; userId: string }>()
defineEmits<{ (e: 'logout'): void }>()
const {
connect, disconnect, send, revoke, messages, conversations, connected,
refreshConversations, loadHistory: fetchHistory, setConversationRead,
getFriends, getGroups, setConversationPinnedState, setConversationMutedState,
removeConversation,
} = useIm()
const activeTab = ref('conversations')
const activeCollapse = ref(['msg'])
const currentTarget = ref<ConversationView | null>(null)
const inputText = ref('')
const msgContainer = ref<HTMLDivElement>()
const friends = ref<string[]>([])
const groups = ref<ImGroup[]>([])
const friendRequests = ref<FriendRequest[]>([])
const loading = ref(false)
const loadingFriends = ref(false)
const loadingGroups = ref(false)
const testing = ref(false)
const friendInput = ref('')
const logs = ref<{ time: string; text: string; type: string }[]>([])
function log(text: string, type: 'info' | 'success' | 'error' = 'info') {
const time = new Date().toLocaleTimeString()
logs.value.unshift({ time, text, type })
if (logs.value.length > 100) logs.value.pop()
}
function formatTime(t: number | string | Date) {
if (!t) return ''
const d = typeof t === 'number' ? new Date(t) : new Date(t)
return d.toLocaleTimeString()
}
function statusType(s: string) {
const map: Record<string, string> = {
SENDING: 'warning', SENT: 'success', DELIVERED: 'info',
READ: 'success', FAILED: 'danger', REVOKED: 'info',
}
return map[s] || 'info'
}
function selectConversation(conv: ConversationView) {
currentTarget.value = conv
messages.value = [] // clear local messages when switching
loadHistory()
}
function startChat(targetId: string, chatType: 'SINGLE' | 'GROUP') {
currentTarget.value = {
targetId, chatType, lastMsgTime: Date.now(), unreadCount: 0, isMuted: false, isPinned: false,
}
messages.value = []
loadHistory()
}
function sendText() {
if (!inputText.value.trim() || !currentTarget.value) return
send({
toId: currentTarget.value.targetId,
chatType: currentTarget.value.chatType,
msgType: 'TEXT',
content: inputText.value.trim(),
})
inputText.value = ''
scrollToBottom()
}
function revokeMsg(msgId: string) {
revoke(msgId).then(() => {
ElMessage.success('消息已撤回')
log(`撤回消息: ${msgId}`, 'success')
}).catch((err: any) => {
ElMessage.error(err.message || '撤回失败')
log(`撤回失败: ${err.message}`, 'error')
})
}
async function loadHistory() {
if (!currentTarget.value) return
loading.value = true
try {
const result = currentTarget.value.chatType === 'SINGLE'
? await fetchHistory(currentTarget.value.targetId, { page: 0, size: 50 })
: await useIm().loadGroupHistory(currentTarget.value.targetId, { page: 0, size: 50 })
log(`加载历史: ${result.content.length} 条, total=${result.totalElements}`, 'success')
// Note: useIm doesn't expose setMessages, we just log for now
} catch (err: any) {
log(`加载历史失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function loadFriends() {
loadingFriends.value = true
try {
friends.value = await getFriends()
log(`好友列表: ${friends.value.length}`, 'success')
} catch (err: any) {
log(`获取好友失败: ${err.message}`, 'error')
} finally {
loadingFriends.value = false
}
}
async function loadGroups() {
loadingGroups.value = true
try {
groups.value = await getGroups()
log(`群组列表: ${groups.value.length}`, 'success')
} catch (err: any) {
log(`获取群组失败: ${err.message}`, 'error')
} finally {
loadingGroups.value = false
}
}
async function addFriend() {
if (!friendInput.value.trim()) return
loading.value = true
try {
const res = await sendFriendRequest(friendInput.value.trim(), 'demo测试')
log(`发送好友请求: ${res.id}`, 'success')
ElMessage.success('好友请求已发送')
} catch (err: any) {
log(`加好友失败: ${err.message}`, 'error')
ElMessage.error(err.message)
} finally {
loading.value = false
}
}
async function loadFriendRequests() {
loading.value = true
try {
friendRequests.value = await listFriendRequests('incoming')
log(`好友请求: ${friendRequests.value.length} 条待处理`, 'success')
if (friendRequests.value.length > 0) {
// Auto accept first pending request for demo
const pending = friendRequests.value.find(r => r.status === 'PENDING')
if (pending) {
await acceptFriendRequest(pending.id)
log(`自动接受好友请求: ${pending.fromUserId}`, 'success')
}
}
} catch (err: any) {
log(`获取好友请求失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function searchMsgs() {
loading.value = true
try {
const result = await searchMessages(null, currentTarget.value?.chatType || 'SINGLE', 'TEXT')
log(`搜索消息: ${result.content.length}`, 'success')
} catch (err: any) {
log(`搜索消息失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function createGroup() {
loading.value = true
try {
// Use http directly or API - demo uses simple approach
const { http } = await import('@xuqm/vue3-sdk')
const group = await http.post('/api/im/groups', {
name: `TestGroup_${Date.now()}`,
memberIds: ['user_b'],
groupType: 'WORK',
})
log(`创建群组成功: ${group.id}`, 'success')
await loadGroups()
} catch (err: any) {
log(`创建群组失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function joinGroup() {
if (!currentTarget.value || currentTarget.value.chatType !== 'GROUP') {
ElMessage.warning('请先选择一个群组')
return
}
loading.value = true
try {
const { sendGroupJoinRequest } = await import('@xuqm/vue3-sdk')
const res = await sendGroupJoinRequest(currentTarget.value.targetId, '申请加入')
log(`申请入群: ${res.id}`, 'success')
} catch (err: any) {
log(`申请入群失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function pinConv() {
if (!currentTarget.value) return
loading.value = true
try {
await setConversationPinnedState(currentTarget.value.targetId, currentTarget.value.chatType, true)
log('会话已置顶', 'success')
await refreshConversations()
} catch (err: any) {
log(`置顶失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function muteConv() {
if (!currentTarget.value) return
loading.value = true
try {
await setConversationMutedState(currentTarget.value.targetId, currentTarget.value.chatType, true)
log('会话已静音', 'success')
} catch (err: any) {
log(`静音失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function deleteConv() {
if (!currentTarget.value) return
loading.value = true
try {
await removeConversation(currentTarget.value.targetId, currentTarget.value.chatType)
log('会话已删除', 'success')
currentTarget.value = null
await refreshConversations()
} catch (err: any) {
log(`删除失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function getProfile() {
loading.value = true
try {
const p = await apiGetProfile(props.userId)
log(`用户资料: ${p.nickname || p.userId}`, 'success')
} catch (err: any) {
log(`获取资料失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
async function updateProfile() {
loading.value = true
try {
const p = await apiUpdateProfile(props.userId, `DemoUser_${Date.now()}`, null, 'MALE')
log(`更新昵称: ${p.nickname}`, 'success')
} catch (err: any) {
log(`更新失败: ${err.message}`, 'error')
} finally {
loading.value = false
}
}
function markConversationAsRead() {
if (!currentTarget.value) return
setConversationRead(currentTarget.value.targetId, currentTarget.value.chatType)
.then(() => {
log('标记已读成功', 'success')
ElMessage.success('已标记已读')
})
.catch((err: any) => {
log(`标记已读失败: ${err.message}`, 'error')
})
}
function scrollToBottom() {
nextTick(() => {
msgContainer.value?.scrollTo({ top: msgContainer.value.scrollHeight, behavior: 'smooth' })
})
}
async function testAllApis() {
testing.value = true
log('========== 开始一键API测试 ==========')
try {
await loadFriends()
await loadGroups()
await loadFriendRequests()
await getProfile()
if (currentTarget.value) {
await loadHistory()
await searchMsgs()
}
log('========== API测试完成 ==========', 'success')
ElMessage.success('API 测试完成,请查看日志')
} catch (err: any) {
log(`测试异常: ${err.message}`, 'error')
} finally {
testing.value = false
}
}
watch(messages, scrollToBottom, { deep: true })
onMounted(() => {
connect()
log('Demo 启动,尝试连接 WebSocket...')
})
onUnmounted(() => {
disconnect()
})
</script>
<style scoped>
.chat-demo {
display: flex;
flex-direction: column;
height: 100vh;
background: #f5f7fa;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: #fff;
border-bottom: 1px solid #e4e7ed;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.user-info {
font-size: 14px;
color: #606266;
}
.main {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: 260px;
background: #fff;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
}
.list-container {
padding: 8px;
overflow-y: auto;
flex: 1;
}
.conv-item {
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
margin-bottom: 4px;
transition: background 0.2s;
}
.conv-item:hover, .conv-item.active {
background: #ecf5ff;
}
.conv-title {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 500;
margin-bottom: 4px;
}
.conv-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #909399;
}
.conv-preview {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.friend-item, .group-item {
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: background 0.2s;
}
.friend-item:hover, .group-item:hover {
background: #f5f7fa;
}
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
background: #fff;
}
.chat-header {
padding: 12px 20px;
border-bottom: 1px solid #e4e7ed;
font-weight: 500;
background: #fafafa;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 16px 20px;
}
.message-row {
display: flex;
margin-bottom: 12px;
}
.message-row.self {
justify-content: flex-end;
}
.message-row.other {
justify-content: flex-start;
}
.message-bubble {
max-width: 60%;
padding: 10px 14px;
border-radius: 12px;
background: #f4f4f5;
}
.message-row.self .message-bubble {
background: #409eff;
color: #fff;
}
.msg-sender {
font-size: 12px;
margin-bottom: 4px;
opacity: 0.8;
}
.msg-content {
word-break: break-word;
line-height: 1.5;
}
.revoked {
font-style: italic;
opacity: 0.6;
}
.msg-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 6px;
font-size: 11px;
}
.message-row.self .msg-meta {
color: rgba(255,255,255,0.85);
}
.input-area {
padding: 12px 20px;
border-top: 1px solid #e4e7ed;
background: #fafafa;
}
.input-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 8px;
}
.test-panel {
width: 300px;
background: #fff;
border-left: 1px solid #e4e7ed;
padding: 12px;
display: flex;
flex-direction: column;
}
.test-panel h4 {
margin: 0 0 12px;
padding-bottom: 8px;
border-bottom: 1px solid #e4e7ed;
}
.log-area {
margin-top: 8px;
}
.log-item {
font-size: 12px;
padding: 4px 0;
border-bottom: 1px dashed #ebeef5;
line-height: 1.4;
}
.log-time {
color: #909399;
margin-right: 6px;
}
.log-item.success .log-text {
color: #67c23a;
}
.log-item.error .log-text {
color: #f56c6c;
}
.log-item.info .log-text {
color: #409eff;
}
</style>

102
src/views/LoginView.vue 普通文件
查看文件

@ -0,0 +1,102 @@
<template>
<div class="login-container">
<el-card class="login-card" shadow="always">
<template #header>
<h2>Vue3 SDK Demo 登录</h2>
</template>
<el-form :model="form" label-width="80px" @submit.prevent="handleLogin">
<el-form-item label="用户ID">
<el-input v-model="form.userId" placeholder="如: user_a" clearable />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" placeholder="123456" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" native-type="submit" :loading="loading" style="width: 100%">
登录
</el-button>
</el-form-item>
</el-form>
<el-divider>快捷账号</el-divider>
<div class="quick-users">
<el-tag
v-for="u in quickUsers"
:key="u"
class="quick-tag"
type="info"
@click="quickLogin(u)"
>
{{ u }}
</el-tag>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
const emit = defineEmits<{ (e: 'login', token: string, userId: string): void }>()
const form = reactive({ userId: 'user_a', password: '123456' })
const loading = ref(false)
const quickUsers = ['user_a', 'user_b', 'user_ascii']
async function handleLogin() {
if (!form.userId || !form.password) {
ElMessage.warning('请填写完整信息')
return
}
await doLogin(form.userId, form.password)
}
function quickLogin(userId: string) {
form.userId = userId
form.password = '123456'
doLogin(userId, '123456')
}
async function doLogin(userId: string, password: string) {
loading.value = true
try {
const res = await fetch('http://192.168.113.37:8085/api/demo/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ appId: 'ak_demo_chat', userId, password }),
})
const json = await res.json()
if (json.code !== 200) {
throw new Error(json.message || '登录失败')
}
ElMessage.success(`登录成功: ${userId}`)
emit('login', json.data.imToken, userId)
} catch (err: any) {
ElMessage.error(err.message || '登录失败')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.login-card {
width: 420px;
}
.quick-users {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
.quick-tag {
cursor: pointer;
}
</style>

19
tsconfig.json 普通文件
查看文件

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src/**/*.ts", "src/**/*.vue"]
}

17
vite.config.ts 普通文件
查看文件

@ -0,0 +1,17 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@xuqm/vue3-sdk': resolve(__dirname, '../XuqmGroup-Vue3SDK/src/index.ts'),
},
},
server: {
port: 5173,
host: true,
},
})