feat(sample): 添加示例应用的核心功能模块
- 实现环境配置管理,支持外部和本地主机模式切换 - 集成Demo API接口,包含登录、注册、文件上传等功能 - 构建附件处理仓库,支持图片、视频、音频和文件发送 - 开发认证仓库,管理用户会话和IM令牌刷新机制 - 添加语音录制功能,支持实时音频消息录制 - 创建依赖注入容器,统一管理应用组件实例 - 实现登录界面,提供用户认证交互功能 - 开发聊天界面,集成消息收发和媒体处理功能
这个提交包含在:
父节点
a6920641ad
当前提交
662df6e090
3
ops-platform/components.d.ts
vendored
3
ops-platform/components.d.ts
vendored
@ -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 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
11
tenant-platform/components.d.ts
vendored
11
tenant-platform/components.d.ts
vendored
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户