feat: support single-domain web deployment docs
这个提交包含在:
父节点
5d06dd52e1
当前提交
946ed35813
25
Dockerfile
普通文件
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 前端文档
|
||||
|
||||
> 当前推荐阅读入口:[`/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
|
||||
|
||||
## 工程结构
|
||||
|
||||
78
docs/ACCESS.md
普通文件
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
普通文件
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
普通文件
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
普通文件
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']
|
||||
}
|
||||
}
|
||||
1
ops-platform/env.d.ts
vendored
1
ops-platform/env.d.ts
vendored
@ -2,6 +2,7 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_BASE_URL?: string
|
||||
readonly BASE_URL: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{ 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"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"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": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": ["vite.config.*"],
|
||||
"include": ["vite.config.*", "vite.config.d.ts"],
|
||||
"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
普通文件
3
ops-platform/vite.config.d.ts
vendored
普通文件
@ -0,0 +1,3 @@
|
||||
interface ImportMeta {
|
||||
readonly url: string
|
||||
}
|
||||
@ -1,18 +1,21 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, new URL('.', import.meta.url).pathname, '')
|
||||
|
||||
return {
|
||||
base: env.VITE_APP_BASE || '/ops/',
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({ resolvers: [ElementPlusResolver()] }),
|
||||
Components({ resolvers: [ElementPlusResolver()] }),
|
||||
],
|
||||
resolve: {
|
||||
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
|
||||
alias: { '@': new URL('./src', import.meta.url).pathname },
|
||||
},
|
||||
server: {
|
||||
port: 5174,
|
||||
@ -20,4 +23,5 @@ export default defineConfig({
|
||||
'/api': { target: 'http://localhost:8081', changeOrigin: true },
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
10
tenant-platform/auto-imports.d.ts
vendored
普通文件
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
普通文件
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']
|
||||
}
|
||||
}
|
||||
1
tenant-platform/src/env.d.ts
vendored
1
tenant-platform/src/env.d.ts
vendored
@ -2,6 +2,7 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_BASE_URL: string
|
||||
readonly BASE_URL: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
|
||||
@ -42,10 +42,6 @@
|
||||
<el-input v-model="verifyCode" placeholder="6位验证码" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleVerify">确认验证</el-button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
@ -66,10 +62,12 @@
|
||||
<el-input v-model="createForm.email" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="creating" @click="handleCreate">创建</el-button>
|
||||
</template>
|
||||
<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>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,20 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"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": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": ["vite.config.*"],
|
||||
"include": ["vite.config.*", "vite.config.d.ts"],
|
||||
"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
普通文件
3
tenant-platform/vite.config.d.ts
vendored
普通文件
@ -0,0 +1,3 @@
|
||||
interface ImportMeta {
|
||||
readonly url: string
|
||||
}
|
||||
@ -1,11 +1,14 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, new URL('.', import.meta.url).pathname, '')
|
||||
|
||||
return {
|
||||
base: env.VITE_APP_BASE || '/',
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
@ -17,7 +20,7 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@': new URL('./src', import.meta.url).pathname,
|
||||
},
|
||||
},
|
||||
server: {
|
||||
@ -29,4 +32,5 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户