feat: support single-domain web deployment docs

这个提交包含在:
XuqmGroup 2026-04-24 10:42:11 +08:00
父节点 5d06dd52e1
当前提交 946ed35813
共有 22 个文件被更改,包括 1740 次插入58 次删除

25
Dockerfile 普通文件
查看文件

@ -0,0 +1,25 @@
FROM node:22-alpine AS build
WORKDIR /workspace
COPY package.json ./package.json
COPY yarn.lock ./yarn.lock
COPY tenant-platform ./tenant-platform
COPY ops-platform ./ops-platform
RUN yarn install --frozen-lockfile
ARG TENANT_APP_BASE=/
ARG OPS_APP_BASE=/ops/
ARG TENANT_API_BASE_URL=/api
ARG OPS_API_BASE_URL=/api
RUN cd tenant-platform && \
VITE_APP_BASE=${TENANT_APP_BASE} VITE_API_BASE_URL=${TENANT_API_BASE_URL} yarn build
RUN cd ops-platform && \
VITE_APP_BASE=${OPS_APP_BASE} VITE_API_BASE_URL=${OPS_API_BASE_URL} yarn build
FROM nginx:1.27-alpine
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /workspace/tenant-platform/dist /usr/share/nginx/html/tenant
COPY --from=build /workspace/ops-platform/dist /usr/share/nginx/html/ops

查看文件

@ -1,5 +1,9 @@
# XuqmGroup Web 前端文档 # XuqmGroup Web 前端文档
> 当前推荐阅读入口:[`/docs/web/README.md`](../docs/web/README.md)
> 仓库内联调文档:[`docs/ACCESS.md`](./docs/ACCESS.md)
> 该文档保留为仓库内说明,线上访问地址、初始化管理员账号与最新前端联调信息请以最新文档为准。
> Vue 3.5 · TypeScript · Vite 6 · Element Plus 2.9 · Pinia 3 > Vue 3.5 · TypeScript · Vite 6 · Element Plus 2.9 · Pinia 3
## 工程结构 ## 工程结构

78
docs/ACCESS.md 普通文件
查看文件

@ -0,0 +1,78 @@
# XuqmGroup Web 访问与联调文档
> 最后更新2026-04-24
## 线上访问地址
| 系统 | 地址 | 说明 |
|------|------|------|
| 租户开放平台 | `https://sentry.xuqinmin.com/` | 主账号注册、登录、应用管理 |
| 运营管理平台 | `https://sentry.xuqinmin.com/ops/` | 内部运营管理入口 |
## 初始化管理员账号
| 字段 | 值 |
|------|----|
| 用户名 | `admin` |
| 初始密码 | `Admin@123456` |
## 前端接口基地址
| 平台 | API Base |
|------|----------|
| tenant-platform | `https://sentry.xuqinmin.com/api` |
| ops-platform | `https://sentry.xuqinmin.com/api` |
## 主要页面
### tenant-platform
| 路径 | 说明 |
|------|------|
| `/login` | 登录 |
| `/register` | 注册 |
| `/forgot-password` | 忘记密码 |
| `/dashboard` | 控制台首页 |
| `/apps` | 应用列表 |
| `/apps/:id` | 应用详情 |
| `/accounts` | 子账号管理 |
### ops-platform
| 路径 | 说明 |
|------|------|
| `/ops/login` | 运营管理员登录 |
| `/ops/tenants` | 租户列表 |
| `/ops/statistics` | 统计面板 |
## 主要联调接口
| 方法 | 路径 | 页面 |
|------|------|------|
| GET | `/api/auth/captcha` | 租户登录 |
| POST | `/api/auth/send-email-code` | 注册、找回密码、子账号验证 |
| POST | `/api/auth/register` | 注册 |
| POST | `/api/auth/login` | 租户登录 |
| POST | `/api/auth/forgot-password` | 忘记密码 |
| POST | `/api/auth/reset-password` | 重置密码 |
| GET | `/api/apps` | 应用列表 |
| GET | `/api/apps/{id}` | 应用详情 |
| POST | `/api/apps` | 创建应用 |
| GET | `/api/sub-accounts` | 子账号列表 |
| POST | `/api/sub-accounts` | 创建子账号 |
| POST | `/api/auth/ops/login` | 运营平台登录 |
| GET | `/api/ops/tenants` | 运营租户管理 |
| GET | `/api/ops/statistics` | 运营统计 |
## 开发环境
| 系统 | 地址 |
|------|------|
| tenant-platform | `http://localhost:5173/` |
| ops-platform | `http://localhost:5174/` |
## 环境变量
```env
VITE_API_BASE_URL=https://sentry.xuqinmin.com/api
```

16
nginx/default.conf 普通文件
查看文件

@ -0,0 +1,16 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html/tenant;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /ops/ {
alias /usr/share/nginx/html/ops/;
try_files $uri $uri/ /ops/index.html;
}
}

10
ops-platform/auto-imports.d.ts vendored 普通文件
查看文件

@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}

35
ops-platform/components.d.ts vendored 普通文件
查看文件

@ -0,0 +1,35 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ElAside: typeof import('element-plus/es')['ElAside']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRow: typeof import('element-plus/es')['ElRow']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

查看文件

@ -2,6 +2,7 @@
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_API_BASE_URL?: string readonly VITE_API_BASE_URL?: string
readonly BASE_URL: string
} }
interface ImportMeta { interface ImportMeta {

查看文件

@ -1,7 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ path: '/login', component: () => import('@/views/auth/LoginView.vue') }, { path: '/login', component: () => import('@/views/auth/LoginView.vue') },
{ {

查看文件

@ -1,9 +1,20 @@
{ {
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"], "exclude": ["src/**/__tests__/*"],
"compilerOptions": { "compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"skipLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]

查看文件

@ -1,7 +1,17 @@
{ {
"extends": "@tsconfig/node22/tsconfig.json", "include": ["vite.config.*", "vite.config.d.ts"],
"include": ["vite.config.*"],
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" "composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"skipLibCheck": true
} }
} }

3
ops-platform/vite.config.d.ts vendored 普通文件
查看文件

@ -0,0 +1,3 @@
interface ImportMeta {
readonly url: string
}

查看文件

@ -1,23 +1,27 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [ const env = loadEnv(mode, new URL('.', import.meta.url).pathname, '')
vue(),
AutoImport({ resolvers: [ElementPlusResolver()] }), return {
Components({ resolvers: [ElementPlusResolver()] }), base: env.VITE_APP_BASE || '/ops/',
], plugins: [
resolve: { vue(),
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }, AutoImport({ resolvers: [ElementPlusResolver()] }),
}, Components({ resolvers: [ElementPlusResolver()] }),
server: { ],
port: 5174, resolve: {
proxy: { alias: { '@': new URL('./src', import.meta.url).pathname },
'/api': { target: 'http://localhost:8081', changeOrigin: true },
}, },
}, server: {
port: 5174,
proxy: {
'/api': { target: 'http://localhost:8081', changeOrigin: true },
},
},
}
}) })

10
tenant-platform/auto-imports.d.ts vendored 普通文件
查看文件

@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}

50
tenant-platform/components.d.ts vendored 普通文件
查看文件

@ -0,0 +1,50 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElRow: typeof import('element-plus/es')['ElRow']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

查看文件

@ -2,6 +2,7 @@
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string readonly VITE_API_BASE_URL: string
readonly BASE_URL: string
} }
interface ImportMeta { interface ImportMeta {

查看文件

@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: '/login', path: '/login',

查看文件

@ -42,10 +42,6 @@
<el-input v-model="verifyCode" placeholder="6位验证码" /> <el-input v-model="verifyCode" placeholder="6位验证码" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer>
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="handleVerify">确认验证</el-button>
</template>
</template> </template>
<template v-else> <template v-else>
@ -66,10 +62,12 @@
<el-input v-model="createForm.email" /> <el-input v-model="createForm.email" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> </template>
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="creating" @click="handleCreate">创建</el-button> <template #footer>
</template> <el-button @click="showDialog = false">取消</el-button>
<el-button v-if="step === 0" type="primary" @click="handleVerify">确认验证</el-button>
<el-button v-else type="primary" :loading="creating" @click="handleCreate">创建</el-button>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>

查看文件

@ -1,9 +1,20 @@
{ {
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"], "exclude": ["src/**/__tests__/*"],
"compilerOptions": { "compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"skipLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]

查看文件

@ -1,7 +1,17 @@
{ {
"extends": "@tsconfig/node22/tsconfig.json", "include": ["vite.config.*", "vite.config.d.ts"],
"include": ["vite.config.*"],
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" "composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"skipLibCheck": true
} }
} }

3
tenant-platform/vite.config.d.ts vendored 普通文件
查看文件

@ -0,0 +1,3 @@
interface ImportMeta {
readonly url: string
}

查看文件

@ -1,32 +1,36 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [ const env = loadEnv(mode, new URL('.', import.meta.url).pathname, '')
vue(),
AutoImport({ return {
resolvers: [ElementPlusResolver()], base: env.VITE_APP_BASE || '/',
}), plugins: [
Components({ vue(),
resolvers: [ElementPlusResolver()], AutoImport({
}), resolvers: [ElementPlusResolver()],
], }),
resolve: { Components({
alias: { resolvers: [ElementPlusResolver()],
'@': fileURLToPath(new URL('./src', import.meta.url)), }),
}, ],
}, resolve: {
server: { alias: {
port: 5173, '@': new URL('./src', import.meta.url).pathname,
proxy: {
'/api': {
target: 'http://localhost:8081',
changeOrigin: true,
}, },
}, },
}, server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8081',
changeOrigin: true,
},
},
},
}
}) })

1398
yarn.lock 普通文件

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