feat(sample): 添加示例应用的核心功能模块

- 实现环境配置管理,支持外部和本地主机模式切换
- 集成Demo API接口,包含登录、注册、文件上传等功能
- 构建附件处理仓库,支持图片、视频、音频和文件发送
- 开发认证仓库,管理用户会话和IM令牌刷新机制
- 添加语音录制功能,支持实时音频消息录制
- 创建依赖注入容器,统一管理应用组件实例
- 实现登录界面,提供用户认证交互功能
- 开发聊天界面,集成消息收发和媒体处理功能
这个提交包含在:
XuqmGroup 2026-04-28 16:08:07 +08:00
父节点 a6920641ad
当前提交 662df6e090
共有 11 个文件被更改,包括 966 次插入7 次删除

查看文件

@ -12,6 +12,7 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
@ -20,8 +21,10 @@ declare module 'vue' {
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElStatistic: typeof import('element-plus/es')['ElStatistic'] ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']

查看文件

@ -4,6 +4,24 @@ const client = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api', baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api',
}) })
if (import.meta.env.DEV) {
client.interceptors.request.use((config) => {
console.debug('[ops-platform][HTTP] request', {
method: config.method?.toUpperCase(),
url: config.baseURL ? `${config.baseURL}${config.url ?? ''}` : config.url,
params: config.params,
})
return config
})
client.interceptors.response.use((res) => {
console.debug('[ops-platform][HTTP] response', {
status: res.status,
url: res.config.url,
})
return res
})
}
client.interceptors.request.use((config) => { client.interceptors.request.use((config) => {
const token = localStorage.getItem('ops_token') const token = localStorage.getItem('ops_token')
if (token) config.headers.Authorization = `Bearer ${token}` if (token) config.headers.Authorization = `Bearer ${token}`

查看文件

@ -17,10 +17,26 @@ export default defineConfig(({ mode }) => {
resolve: { resolve: {
alias: { '@': new URL('./src', import.meta.url).pathname }, alias: { '@': new URL('./src', import.meta.url).pathname },
}, },
build: {
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (!id.includes('node_modules')) return undefined
if (id.includes('element-plus')) return 'element-plus'
if (id.includes('@element-plus/icons-vue')) return 'element-plus-icons'
if (id.includes('vue-router')) return 'vue-router'
if (id.includes('pinia')) return 'pinia'
if (id.includes('axios')) return 'axios'
return 'vendor'
},
},
},
},
server: { server: {
port: 5174, port: 5174,
proxy: { proxy: {
'/api': { target: 'http://localhost:8081', changeOrigin: true }, '/api': { target: 'http://192.168.116.9:8081', changeOrigin: true },
}, },
}, },
} }

查看文件

@ -13,22 +13,31 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElStatistic: typeof import('element-plus/es')['ElStatistic'] ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElStep: typeof import('element-plus/es')['ElStep'] ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps'] ElSteps: typeof import('element-plus/es')['ElSteps']
@ -41,6 +50,8 @@ declare module 'vue' {
ElText: typeof import('element-plus/es')['ElText'] ElText: typeof import('element-plus/es')['ElText']
ElTimeline: typeof import('element-plus/es')['ElTimeline'] ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }

查看文件

@ -25,6 +25,7 @@ export interface FeatureService {
platform: 'ANDROID' | 'IOS' | 'HARMONY' platform: 'ANDROID' | 'IOS' | 'HARMONY'
serviceType: 'IM' | 'PUSH' | 'UPDATE' serviceType: 'IM' | 'PUSH' | 'UPDATE'
enabled: boolean enabled: boolean
config?: string | null
createdAt: string createdAt: string
} }
@ -47,6 +48,11 @@ export const appApi = {
params: { platform, serviceType, enable }, params: { platform, serviceType, enable },
}), }),
updateServiceConfig: (appId: string, platform: string, serviceType: string, allowStrangerMessage: boolean) =>
client.put<{ data: FeatureService }>(`/apps/${appId}/services/config`, null, {
params: { platform, serviceType, allowStrangerMessage },
}),
requestSecretVerify: (appId: string, purpose: 'REVEAL_SECRET' | 'RESET_SECRET') => requestSecretVerify: (appId: string, purpose: 'REVEAL_SECRET' | 'RESET_SECRET') =>
client.post<{ data: null }>(`/apps/${appId}/request-secret-verify`, null, { client.post<{ data: null }>(`/apps/${appId}/request-secret-verify`, null, {
params: { purpose }, params: { purpose },

查看文件

@ -7,6 +7,24 @@ const client = axios.create({
timeout: 15000, timeout: 15000,
}) })
if (import.meta.env.DEV) {
client.interceptors.request.use((config) => {
console.debug('[tenant-platform][HTTP] request', {
method: config.method?.toUpperCase(),
url: config.baseURL ? `${config.baseURL}${config.url ?? ''}` : config.url,
params: config.params,
})
return config
})
client.interceptors.response.use((res) => {
console.debug('[tenant-platform][HTTP] response', {
status: res.status,
url: res.config.url,
})
return res
})
}
client.interceptors.request.use((config) => { client.interceptors.request.use((config) => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) { if (token) {

查看文件

@ -1,10 +1,28 @@
import axios from 'axios' import axios from 'axios'
const imClient = axios.create({ const imClient = axios.create({
baseURL: 'https://dev.xuqinmin.com', baseURL: 'http://192.168.116.9:8082',
timeout: 15000, timeout: 15000,
}) })
if (import.meta.env.DEV) {
imClient.interceptors.request.use((config) => {
console.debug('[tenant-platform][IM] request', {
method: config.method?.toUpperCase(),
url: config.baseURL ? `${config.baseURL}${config.url ?? ''}` : config.url,
params: config.params,
})
return config
})
imClient.interceptors.response.use((res) => {
console.debug('[tenant-platform][IM] response', {
status: res.status,
url: res.config.url,
})
return res
})
}
imClient.interceptors.request.use((config) => { imClient.interceptors.request.use((config) => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}` if (token) config.headers.Authorization = `Bearer ${token}`

查看文件

@ -1,10 +1,28 @@
import axios from 'axios' import axios from 'axios'
const updateClient = axios.create({ const updateClient = axios.create({
baseURL: 'https://dev.xuqinmin.com', baseURL: 'http://192.168.116.9:8084',
timeout: 30000, timeout: 30000,
}) })
if (import.meta.env.DEV) {
updateClient.interceptors.request.use((config) => {
console.debug('[tenant-platform][UPDATE] request', {
method: config.method?.toUpperCase(),
url: config.baseURL ? `${config.baseURL}${config.url ?? ''}` : config.url,
params: config.params,
})
return config
})
updateClient.interceptors.response.use((res) => {
console.debug('[tenant-platform][UPDATE] response', {
status: res.status,
url: res.config.url,
})
return res
})
}
updateClient.interceptors.request.use((config) => { updateClient.interceptors.request.use((config) => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}` if (token) config.headers.Authorization = `Bearer ${token}`

查看文件

@ -56,6 +56,13 @@
版本管理 版本管理
</el-button> </el-button>
</div> </div>
<div v-if="svcType === 'IM'" class="service-config-row">
<span>允许陌生人发消息</span>
<el-switch
:model-value="allowStrangerMessage(activePlatform)"
@change="(val: boolean) => onToggleStrangerMessage(activePlatform, val)"
/>
</div>
</template> </template>
<template v-else> <template v-else>
<div style="margin-top:10px"> <div style="margin-top:10px">
@ -143,6 +150,20 @@ function isEnabled(platform: string, svcType: string) {
) )
} }
function getService(platform: string, svcType: string) {
return services.value.find(s => s.platform === platform && s.serviceType === svcType) ?? null
}
function allowStrangerMessage(platform: string) {
const service = getService(platform, 'IM')
if (!service?.config) return false
try {
return Boolean(JSON.parse(service.config).allowStrangerMessage)
} catch {
return false
}
}
function serviceLabel(type: string) { function serviceLabel(type: string) {
return { IM: '即时通讯 (IM)', PUSH: '离线推送', UPDATE: '版本管理' }[type] ?? type return { IM: '即时通讯 (IM)', PUSH: '离线推送', UPDATE: '版本管理' }[type] ?? type
} }
@ -170,6 +191,12 @@ async function onToggleService(platform: string, svcType: string, enable: boolea
} }
} }
async function onToggleStrangerMessage(platform: string, allow: boolean) {
await appApi.updateServiceConfig(route.params.id as string, platform, 'IM', allow)
ElMessage.success(allow ? '已允许陌生人发消息' : '已关闭陌生人发消息')
loadData()
}
function openActivationRequest(platform: string, svcType: string) { function openActivationRequest(platform: string, svcType: string) {
activationForm.value = { platform, serviceType: svcType, reason: '' } activationForm.value = { platform, serviceType: svcType, reason: '' }
showActivationDialog.value = true showActivationDialog.value = true
@ -251,4 +278,5 @@ onMounted(loadData)
.service-card { border: 1px solid #e8e8e8; } .service-card { border: 1px solid #e8e8e8; }
.service-header { display: flex; justify-content: space-between; align-items: center; font-weight: 500; } .service-header { display: flex; justify-content: space-between; align-items: center; font-weight: 500; }
.service-name { font-size: 15px; } .service-name { font-size: 15px; }
.service-config-row { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; font-size: 13px; color: #666; }
</style> </style>

查看文件

@ -23,11 +23,27 @@ export default defineConfig(({ mode }) => {
'@': new URL('./src', import.meta.url).pathname, '@': new URL('./src', import.meta.url).pathname,
}, },
}, },
build: {
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (!id.includes('node_modules')) return undefined
if (id.includes('element-plus')) return 'element-plus'
if (id.includes('@element-plus/icons-vue')) return 'element-plus-icons'
if (id.includes('vue-router')) return 'vue-router'
if (id.includes('pinia')) return 'pinia'
if (id.includes('axios')) return 'axios'
return 'vendor'
},
},
},
},
server: { server: {
port: 5173, port: 5173,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8081', target: 'http://192.168.116.9:8081',
changeOrigin: true, changeOrigin: true,
}, },
}, },

813
yarn.lock

文件差异内容过多而无法显示 加载差异