签入版本

This commit is contained in:
2026-10-11 19:06:33 +08:00
parent 17d28de8b7
commit 4084c6bd17
674 changed files with 58855 additions and 1892 deletions
@@ -87,7 +87,7 @@ export const sharedConfig = defineConfig({
socialLinks: [ socialLinks: [
{ {
icon: "github", icon: "github",
link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue",
}, },
], ],
@@ -98,7 +98,7 @@ export const sharedConfig = defineConfig({
}, },
editLink: { editLink: {
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue", pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue",
text: "对本页提出修改建议", text: "对本页提出修改建议",
}, },
@@ -1,9 +1,9 @@
import type { DefaultTheme, LocaleSpecificConfig } from "vitepress"; import type { DefaultTheme, LocaleSpecificConfig } from "vitepress";
export const META_URL = "https://www.rxthink.cn"; export const META_URL = "https://www.rxthink.cn";
export const META_TITLE = "RXThinkCMF Laravel13+ElementPlus 开发文档"; export const META_TITLE = "RXThinkCMF Laravel13+AntDesign 开发文档";
export const META_DESCRIPTION = export const META_DESCRIPTION =
"RXThinkCMF 企业级前后端分离后台管理系统 - Laravel 13 + Vue3 + ElementPlus"; "RXThinkCMF 企业级前后端分离后台管理系统 - Laravel 13 + Vue3 + AntDesign";
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = { export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
description: META_DESCRIPTION, description: META_DESCRIPTION,
@@ -20,7 +20,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
var _hmt = _hmt || []; var _hmt = _hmt || [];
(function() { (function() {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?4f06f01017cdd5c1a95a58a6787f4d10"; hm.src = "https://hm.baidu.com/hm.js?824f0facb10c760fb4e50e7d4cc77307";
var s = document.getElementsByTagName("script")[0]; var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
@@ -30,7 +30,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
themeConfig: { themeConfig: {
editLink: { editLink: {
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue", pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue",
text: "对本页提出修改建议", text: "对本页提出修改建议",
}, },
@@ -160,10 +160,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
text: "相关链接", text: "相关链接",
items: [ items: [
{ text: "官方网站", link: "https://www.rxthink.cn" }, { text: "官方网站", link: "https://www.rxthink.cn" },
{ text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue" }, { text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue" },
{ text: "Laravel 官网", link: "https://laravel.com/" }, { text: "Laravel 官网", link: "https://laravel.com/" },
{ text: "Vue3 文档", link: "https://cn.vuejs.org/" }, { text: "Vue3 文档", link: "https://cn.vuejs.org/" },
{ text: "ElementPlus", link: "https://element-plus.org/zh-CN/" }, { text: "AntDesign Vue", link: "https://antd.vuejs.org/" },
], ],
}, },
], ],
@@ -503,7 +503,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: "L. 贡献指南", link: "/zh/other/contributing.html" }, { text: "L. 贡献指南", link: "/zh/other/contributing.html" },
{ text: "M. 免责声明", link: "/zh/other/declare.html" }, { text: "M. 免责声明", link: "/zh/other/declare.html" },
{ text: "N. 广告赞助", link: "/zh/other/sponsor.html" }, { text: "N. 广告赞助", link: "/zh/other/sponsor.html" },
{ text: "O. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue" }, { text: "O. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue" },
], ],
}, },
], ],
+1 -1
View File
@@ -37,7 +37,7 @@ features:
linkText: 查看更多 linkText: 查看更多
rel: noreferrer rel: noreferrer
- title: 🚀 全新技术栈 - title: 🚀 全新技术栈
details: Laravel 13、PHP 8.2、Vue3、TypeScript、Vite、Pinia、AntDesign 等技术栈,采用主流前后端分离架构实现。 details: Laravel 13、PHP 8.3、Vue3、TypeScript、Vite、Pinia、AntDesign 等技术栈,采用主流前后端分离架构实现。
link: /zh/tech-stack.html link: /zh/tech-stack.html
linkText: 查看更多 linkText: 查看更多
rel: noreferrer rel: noreferrer
@@ -141,14 +141,15 @@ export function getDictItemByCode(code) {
```vue ```vue
<template> <template>
<el-select v-model="formData.status" placeholder="请选择状态"> <a-select v-model:value="formData.status" placeholder="请选择状态" style="width: 100%">
<el-option <a-select-option
v-for="item in statusOptions" v-for="item in statusOptions"
:key="item.value" :key="item.value"
:label="item.name"
:value="item.value" :value="item.value"
/> >
</el-select> {{ item.name }}
</a-select-option>
</a-select>
</template> </template>
<script setup> <script setup>
@@ -86,6 +86,8 @@
## 前端错误处理 ## 前端错误处理
```typescript ```typescript
import { message } from 'ant-design-vue';
// Axios 响应拦截器 // Axios 响应拦截器
axios.interceptors.response.use(response => { axios.interceptors.response.use(response => {
const { code, msg, data } = response.data; const { code, msg, data } = response.data;
@@ -101,12 +103,12 @@ axios.interceptors.response.use(response => {
} }
if (code === 403 || response.status === 403) { if (code === 403 || response.status === 403) {
ElMessage.error(msg || '无权限'); message.error(msg || '无权限');
return; return;
} }
// 其他错误 // 其他错误
ElMessage.error(msg || '操作失败'); message.error(msg || '操作失败');
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
}); });
``` ```
@@ -286,16 +286,16 @@ axios.interceptors.response.use(
// 权限不足 // 权限不足
if (code === 403 || response.status === 403) { if (code === 403 || response.status === 403) {
ElMessage.error(msg || '无访问权限'); message.error(msg || '无访问权限');
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
} }
// 其他业务错误 // 其他业务错误
ElMessage.error(msg || '操作失败'); message.error(msg || '操作失败');
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
}, },
error => { error => {
ElMessage.error('网络错误'); message.error('网络错误');
return Promise.reject(error); return Promise.reject(error);
} }
); );
@@ -320,9 +320,9 @@ const handleImport = async (file) => {
const res = await importUsers(file); const res = await importUsers(file);
// res = { count: 8, errors: [{ row: 3, name: "...", reason: "..." }] } // res = { count: 8, errors: [{ row: 3, name: "...", reason: "..." }] }
if (res.errors.length === 0) { if (res.errors.length === 0) {
ElMessage.success(`成功导入 ${res.count} 条`); message.success(`成功导入 ${res.count} 条`);
} else { } else {
ElMessage.warning(`成功 ${res.count} 条,失败 ${res.errors.length} 条`); message.warning(`成功 ${res.count} 条,失败 ${res.errors.length} 条`);
} }
}; };
``` ```
@@ -164,11 +164,11 @@ Authorization: Bearer <access_token>
```bash ```bash
# curl # curl
curl http://localhost:8000/api/user/page \ curl http://admin.laravel13.antdvue/api/user/page \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
# curl(带分页参数) # curl(带分页参数)
curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \ curl "http://admin.laravel13.antdvue/api/user/page?pageNo=1&pageSize=10" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
``` ```
@@ -363,7 +363,7 @@ Authorization: Bearer <access_token>
前端路由守卫支持通过 URL 参数传递 Token,适用于 SSO 单点登录场景: 前端路由守卫支持通过 URL 参数传递 Token,适用于 SSO 单点登录场景:
```text ```text
http://localhost:8001/?token=<access_token> http://localhost:8002/?token=<access_token>
``` ```
**前端处理逻辑:** **前端处理逻辑:**
@@ -403,14 +403,17 @@ config.headers['Authorization'] = `Bearer ${token}`;
// code === 401(ResultEnum.TIMEOUT) // code === 401(ResultEnum.TIMEOUT)
case ResultEnum.TIMEOUT: case ResultEnum.TIMEOUT:
if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return; if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return;
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', { Modal.warning({
type: 'warning', title: '提示',
}) content: '登录身份已失效,请重新登录!',
.then(() => { closable: false,
maskClosable: false,
okText: '确定',
onOk: () => {
storage.clear(); storage.clear();
window.location.href = PageEnum.BASE_LOGIN; // '/login' window.location.href = PageEnum.BASE_LOGIN; // '/login'
}) },
.catch(() => {}); });
break; break;
``` ```
@@ -32,7 +32,7 @@
"ok": true, "ok": true,
"msg": "上传成功", "msg": "上传成功",
"data": { "data": {
"fileUrl": "http://localhost:8000/uploads/temp/20261002/123456_a1b2c3d4e5f6.jpg", "fileUrl": "http://admin.laravel13.antdvue/uploads/temp/20261002/123456_a1b2c3d4e5f6.jpg",
"filePath": "temp/20261002/123456_a1b2c3d4e5f6.jpg", "filePath": "temp/20261002/123456_a1b2c3d4e5f6.jpg",
"originalName": "photo.jpg", "originalName": "photo.jpg",
"fileSize": 102400, "fileSize": 102400,
@@ -47,7 +47,7 @@
| 字段 | 类型 | 说明 | 示例 | | 字段 | 类型 | 说明 | 示例 |
|------|------|------|------| |------|------|------|------|
| `fileUrl` | string | 可访问 URL(`FILE_DOMAIN_URL` 为空时等于相对路径) | `http://localhost:8000/uploads/temp/...` | | `fileUrl` | string | 可访问 URL(`FILE_DOMAIN_URL` 为空时等于相对路径) | `http://admin.laravel13.antdvue/uploads/temp/...` |
| `filePath` | string | 相对存储路径(**提交表单建议用此字段**) | `temp/20261002/123456_a1b2....jpg` | | `filePath` | string | 相对存储路径(**提交表单建议用此字段**) | `temp/20261002/123456_a1b2....jpg` |
| `originalName` | string | 原始文件名 | `photo.jpg` | | `originalName` | string | 原始文件名 | `photo.jpg` |
| `fileSize` | number | 文件大小(字节) | `102400` | | `fileSize` | number | 文件大小(字节) | `102400` |
@@ -102,52 +102,56 @@ gif, jpg, jpeg, png, bmp, webp
## 前端使用 ## 前端使用
### Element Plus Upload 组件 ### AntDesign Upload 组件
```vue ```vue
<template> <template>
<!-- 上传文件(默认 type=file) --> <!-- 上传文件(默认 type=file) -->
<el-upload <a-upload
:action="'/api/upload/uploadFile'" :action="'/api/upload/uploadFile'"
:headers="{ Authorization: `Bearer ${token}` }" :headers="{ Authorization: `Bearer ${token}` }"
:on-success="handleUploadSuccess"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:show-upload-list="false"
@change="handleUploadChange"
> >
<el-button type="primary">上传文件</el-button> <a-button type="primary">上传文件</a-button>
</el-upload> </a-upload>
<!-- 上传图片(指定 type=image) --> <!-- 上传图片(指定 type=image) -->
<el-upload <a-upload
:action="'/api/upload/uploadFile?type=image'" :action="'/api/upload/uploadFile?type=image'"
:data="{ type: 'image' }" :data="{ type: 'image' }"
:headers="{ Authorization: `Bearer ${token}` }" :headers="{ Authorization: `Bearer ${token}` }"
:on-success="handleImageSuccess" :show-upload-list="false"
accept="image/*" accept="image/*"
@change="handleImageChange"
> >
<el-button type="primary">上传图片</el-button> <a-button type="primary">上传图片</a-button>
</el-upload> </a-upload>
</template> </template>
<script setup> <script setup>
import { message } from 'ant-design-vue';
const token = localStorage.getItem('access_token'); const token = localStorage.getItem('access_token');
const beforeUpload = (file) => { const beforeUpload = (file) => {
const isLt10M = file.size / 1024 / 1024 < 10; const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) { if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB'); message.error('文件大小不能超过 10MB');
} }
return isLt10M; return isLt10M;
}; };
const handleUploadSuccess = (response) => { const handleUploadChange = ({ file }) => {
if (response.code === 0) { if (file.status === 'done' && file.response.code === 0) {
form.value.file = response.data.filePath; // 建议存相对路径 form.value.file = file.response.data.filePath; // 建议存相对路径
} }
}; };
const handleImageSuccess = (response) => { const handleImageChange = ({ file }) => {
if (response.code === 0) { if (file.status === 'done' && file.response.code === 0) {
form.value.avatar = response.data.filePath; form.value.avatar = file.response.data.filePath;
} }
}; };
</script> </script>
@@ -167,7 +171,7 @@ const res = await http.request({
headers: { 'Content-Type': 'multipart/form-data' }, headers: { 'Content-Type': 'multipart/form-data' },
}); });
// res.filePath = "temp/20261002/123456_a1b2....jpg" // res.filePath = "temp/20261002/123456_a1b2....jpg"
// res.fileUrl = "http://localhost:8000/uploads/temp/20261002/..." // res.fileUrl = "http://admin.laravel13.antdvue/uploads/temp/20261002/..."
// 上传图片(指定 type=image) // 上传图片(指定 type=image)
const res2 = await http.request({ const res2 = await http.request({
@@ -199,25 +203,25 @@ await exampleAdd({
```vue ```vue
<template> <template>
<div class="avatar-upload"> <div class="avatar-upload">
<el-avatar :src="form.avatar" :size="80" /> <a-avatar :src="form.avatar" :size="80" />
<el-upload <a-upload
:action="'/api/upload/uploadFile?type=image'" :action="'/api/upload/uploadFile?type=image'"
:headers="{ Authorization: `Bearer ${token}` }" :headers="{ Authorization: `Bearer ${token}` }"
:show-file-list="false" :show-upload-list="false"
:on-success="handleAvatarSuccess"
accept="image/*" accept="image/*"
@change="handleAvatarChange"
> >
<el-button size="small">更换头像</el-button> <a-button size="small">更换头像</a-button>
</el-upload> </a-upload>
</div> </div>
</template> </template>
<script setup> <script setup>
const form = ref({ avatar: '' }); const form = ref({ avatar: '' });
const handleAvatarSuccess = (response) => { const handleAvatarChange = ({ file }) => {
if (response.code === 0) { if (file.status === 'done' && file.response.code === 0) {
form.value.avatar = response.data.filePath; form.value.avatar = file.response.data.filePath;
} }
}; };
</script> </script>
@@ -1,7 +1,7 @@
# G. 前端组件速查 # G. 前端组件速查
:::tip 概述 :::tip 概述
公共组件定义在 `ui/src/components/` 目录下。其中 `PageWrapper`、`PageFooter`、`BasicTable`、`BasicForm`、`NumberInput`、`PriceInput`、`pagination`、`icon` 已在 `ui/src/plugins/customComponents.ts` 中**全局注册**,模板里可直接使用;其余组件按需 import。 公共组件定义在 `ui/src/components/` 目录下。其中 `PageWrapper`、`PageFooter`、`BasicTable`、`BasicForm`、`NumberInput`、`pagination` 已在 `ui/src/plugins/customComponents.ts` 中**全局注册**,模板里可直接使用;其余组件按需 import(图标选择器 `IconPicker` 由 `unplugin-vue-components` 按需自动导入)。
::: :::
## 全局注册组件 ## 全局注册组件
@@ -13,10 +13,8 @@
| `PageWrapper` / `PageFooter` | `components/Page/` | 页面容器 / 页脚 | | `PageWrapper` / `PageFooter` | `components/Page/` | 页面容器 / 页脚 |
| `BasicTable` | `components/Table/` | 数据表格(通过 props 传数据) | | `BasicTable` | `components/Table/` | 数据表格(通过 props 传数据) |
| `BasicForm` | `components/Form/` | 动态表单(配合 `useForm` 的 register) | | `BasicForm` | `components/Form/` | 动态表单(配合 `useForm` 的 register) |
| `NumberInput` | `components/numberInput/` | 数字输入 | | `NumberInput` | `components/numberInput/` | 数字输入(基于 `a-input` 封装) |
| `PriceInput` | `components/numberInput/` | 价格输入 |
| `pagination` | `components/pagination/` | 分页 | | `pagination` | `components/pagination/` | 分页 |
| `icon` | `components/icon/` | 图标 |
## 数据展示 ## 数据展示
@@ -26,7 +24,6 @@
| PageWrapper | `components/Page/` | 页面容器 | 全局注册 | | PageWrapper | `components/Page/` | 页面容器 | 全局注册 |
| CountTo | `components/CountTo/` | 数字动画 | 按需 import | | CountTo | `components/CountTo/` | 数字动画 | 按需 import |
| Qrcode | `components/Qrcode/` | 二维码 | 按需 import | | Qrcode | `components/Qrcode/` | 二维码 | 按需 import |
| TableSelect | `components/TableSelect/` | 表格选择器 | 按需 import |
## 数据录入 ## 数据录入
@@ -41,7 +38,7 @@
| ChinaArea | `components/ChinaArea/` | 省市区联动 | 按需 import | | ChinaArea | `components/ChinaArea/` | 省市区联动 | 按需 import |
| Region | `components/Region/` | 行政区划 | 按需 import | | Region | `components/Region/` | 行政区划 | 按需 import |
| Cropper | `components/Cropper/` | 头像裁剪 | 按需 import | | Cropper | `components/Cropper/` | 头像裁剪 | 按需 import |
| NumberInput / PriceInput | `components/numberInput/`、`priceInput/` | 数字 / 价格输入 | 全局注册 | | NumberInput | `components/numberInput/` | 数字输入 | 全局注册 |
## Excel 与导入导出 ## Excel 与导入导出
@@ -57,7 +54,6 @@
| 组件 | 路径 | 说明 | | 组件 | 路径 | 说明 |
|------|------|------| |------|------|------|
| Authority | `components/Authority/` | 权限组件(根据权限显示/隐藏) | | Authority | `components/Authority/` | 权限组件(根据权限显示/隐藏) |
| CheckToken | `components/CheckToken/` | Token 校验组件 |
| Lockscreen | `components/Lockscreen/` | 锁屏组件 | | Lockscreen | `components/Lockscreen/` | 锁屏组件 |
| Websocket | `components/Websocket/` | WebSocket 组件 | | Websocket | `components/Websocket/` | WebSocket 组件 |
| Render | `components/Render/` | 函数式渲染组件 | | Render | `components/Render/` | 函数式渲染组件 |
@@ -92,12 +88,12 @@ const [register, { getFieldsValue }] = useForm({
:row-key="(row) => row.id" :row-key="(row) => row.id"
ref="tableRef" ref="tableRef"
:actionColumn="actionColumn" :actionColumn="actionColumn"
@selection-change="onSelectionChange" :row-selection="{ onChange: onSelectionChange }"
> >
<template #tableTitle> <template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:example:add']"> <a-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
添加案例演示 添加案例演示
</el-button> </a-button>
</template> </template>
</BasicTable> </BasicTable>
</template> </template>
@@ -117,7 +117,7 @@
| `VITE_GLOB_API_URL_PREFIX` | API 前缀(默认 `/api`) | | `VITE_GLOB_API_URL_PREFIX` | API 前缀(默认 `/api`) |
| `VITE_GLOB_UPLOAD_URL` | 图片上传地址(跨域部署时配置) | | `VITE_GLOB_UPLOAD_URL` | 图片上传地址(跨域部署时配置) |
| `VITE_GLOB_IMG_URL` | 图片前缀地址 | | `VITE_GLOB_IMG_URL` | 图片前缀地址 |
| `VITE_PORT` | 开发端口(默认 8001) | | `VITE_PORT` | 开发端口(默认 8002) |
| `VITE_DROP_CONSOLE` | 构建时删除 console | | `VITE_DROP_CONSOLE` | 构建时删除 console |
## 配置文件对照 ## 配置文件对照
@@ -6,11 +6,11 @@
```text ```text
document/ document/
├── mysql/rxthinkcmf.laravel13.elevue.sql # MySQL 版本(约 216MB) ├── mysql/rxthinkcmf.laravel13.antdvue.sql # MySQL 版本(约 216MB)
├── postgresql/rxthinkcmf.laravel13.elevue.sql # PostgreSQL 版本 ├── postgresql/rxthinkcmf.laravel13.antdvue.sql # PostgreSQL 版本
├── sqlserver/rxthinkcmf.laravel13.elevue.sql # SQL Server 版本 ├── sqlserver/rxthinkcmf.laravel13.antdvue.sql # SQL Server 版本
├── oracle/rxthinkcmf.laravel13.elevue.sql # Oracle 版本 ├── oracle/rxthinkcmf.laravel13.antdvue.sql # Oracle 版本
└── sqlite/rxthinkcmf.laravel13.elevue.sql # SQLite 版本 └── sqlite/rxthinkcmf.laravel13.antdvue.sql # SQLite 版本
``` ```
各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。 各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。
@@ -66,7 +66,6 @@ export enum PageEnum {
| 文件 | 导出 | 说明 | | 文件 | 导出 | 说明 |
|------|------|------| |------|------|------|
| `avatarEnum.ts` | `avatarEnum` | 默认头像资源路径(avatar-1 ~ 6) |
| `breakpointEnum.ts` | `sizeEnum` / `screenEnum` / `screenMap` | 响应式断点(XS 480 ~ XXL 1600) | | `breakpointEnum.ts` | `sizeEnum` / `screenEnum` / `screenMap` | 响应式断点(XS 480 ~ XXL 1600) |
| `cacheEnum.ts` | `TOKEN_KEY`、`USER_INFO_KEY`、`ROLES_KEY` 等 | 本地缓存键名常量 | | `cacheEnum.ts` | `TOKEN_KEY`、`USER_INFO_KEY`、`ROLES_KEY` 等 | 本地缓存键名常量 |
| `permissionsEnum.ts` | `PermissionsEnum`(interface) | 权限项类型 `{ value, label }` | | `permissionsEnum.ts` | `PermissionsEnum`(interface) | 权限项类型 `{ value, label }` |
@@ -29,7 +29,7 @@
| TypeScript | - | JavaScript 超集,类型安全 | | TypeScript | - | JavaScript 超集,类型安全 |
| Vite | - | 前端构建工具 | | Vite | - | 前端构建工具 |
| Pinia | - | Vue 3 官方状态管理库 | | Pinia | - | Vue 3 官方状态管理库 |
| ElementPlus | - | Vue 3 UI 组件库 | | AntDesign Vue | - | Vue 3 UI 组件库(ant-design-vue) |
| Axios | - | HTTP 请求库 | | Axios | - | HTTP 请求库 |
| TailwindCSS | - | 原子化 CSS 框架 | | TailwindCSS | - | 原子化 CSS 框架 |
| TinyMce | - | 富文本编辑器 | | TinyMce | - | 富文本编辑器 |
@@ -85,29 +85,6 @@ await userStore.logout();
--- ---
## ossConfigStore
**文件**:`ui/src/store/modules/ossConfig.ts`
| 字段 | 说明 |
|------|------|
| `ossConfig` | 云存储配置 |
路由守卫中初始化,用于文件上传。
---
## designSettingStore
**文件**:`ui/src/store/modules/designSetting.ts`(id:`app-design-setting`)
| 字段 | 说明 |
|------|------|
| `appTheme` | 主题色 |
| `darkMode` | 是否暗色模式 |
---
## tabsViewStore ## tabsViewStore
**文件**:`ui/src/store/modules/tabsView.ts`(id:`app-tabs-view`) **文件**:`ui/src/store/modules/tabsView.ts`(id:`app-tabs-view`)
@@ -293,7 +293,7 @@ ALTER TABLE laravel_city ADD INDEX idx_parent_code (parent_code);
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -303,7 +303,7 @@ DB_PREFIX=laravel_
DB_CONNECTION=pgsql DB_CONNECTION=pgsql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=5432 DB_PORT=5432
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=postgres DB_USERNAME=postgres
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -313,7 +313,7 @@ DB_PREFIX=laravel_
DB_CONNECTION=sqlsrv DB_CONNECTION=sqlsrv
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=1433 DB_PORT=1433
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=sa DB_USERNAME=sa
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -323,7 +323,7 @@ DB_PREFIX=laravel_
DB_CONNECTION=oracle DB_CONNECTION=oracle
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=1521 DB_PORT=1521
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -383,15 +383,15 @@ Eloquent 的方言层自动处理大部分 SQL 差异,切换数据库只需修
```text ```text
document/ document/
├── mysql/ ├── mysql/
│ └── rxthinkcmf.laravel13.elevue.sql # MySQL 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # MySQL 版本
├── postgresql/ ├── postgresql/
│ └── rxthinkcmf.laravel13.elevue.sql # PostgreSQL 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # PostgreSQL 版本
├── sqlserver/ ├── sqlserver/
│ └── rxthinkcmf.laravel13.elevue.sql # SQL Server 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # SQL Server 版本
├── oracle/ ├── oracle/
│ └── rxthinkcmf.laravel13.elevue.sql # Oracle 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # Oracle 版本
└── sqlite/ └── sqlite/
└── rxthinkcmf.laravel13.elevue.sql # SQLite 版本 └── rxthinkcmf.laravel13.antdvue.sql # SQLite 版本
``` ```
各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。 各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。
@@ -1,7 +1,7 @@
# 4.4 前端分层架构 # 4.4 前端分层架构
:::tip 概述 :::tip 概述
前端采用 Vue 3 + TypeScript 组件化架构,分为 View(视图)→ API(接口)→ Store(状态)→ HTTP(请求)四层。辅以 Router(路由)、Directives(指令)、Hooks(组合式函数)、Utils(工具)等支撑模块。前端为本仓库 `ui/` 目录内的 EleVue 管理后台。 前端采用 Vue 3 + TypeScript 组件化架构,分为 View(视图)→ API(接口)→ Store(状态)→ HTTP(请求)四层。辅以 Router(路由)、Directives(指令)、Hooks(组合式函数)、Utils(工具)等支撑模块。前端为本仓库 `ui/` 目录内的 AntdVue 管理后台。
::: :::
## 架构全景 ## 架构全景
@@ -81,33 +81,30 @@ src/views/
```vue ```vue
<template> <template>
<!-- 搜索区 --> <!-- 搜索区 -->
<el-form :model="searchForm" inline> <a-form :model="searchForm" layout="inline">
<el-form-item label="名称"> <a-form-item label="名称">
<el-input v-model="searchForm.name" /> <a-input v-model:value="searchForm.name" />
</el-form-item> </a-form-item>
<el-form-item> <a-form-item>
<el-button type="primary" @click="fetchData">搜索</el-button> <a-button type="primary" @click="fetchData">搜索</a-button>
</el-form-item> </a-form-item>
</el-form> </a-form>
<!-- 操作栏 --> <!-- 操作栏 -->
<el-button v-perm="'sys:example:add'" type="primary" @click="handleAdd">新增</el-button> <a-button v-perm="'sys:example:add'" type="primary" @click="handleAdd">新增</a-button>
<!-- 数据表格 --> <!-- 数据表格 -->
<el-table :data="tableData"> <a-table :data-source="tableData" :columns="columns" :pagination="false" row-key="id">
<el-table-column prop="id" label="ID" /> <template #bodyCell="{ column, record }">
<el-table-column prop="name" label="名称" /> <template v-if="column.key === 'action'">
<el-table-column prop="statusText" label="状态" /> <a-button v-perm="'sys:example:update'" @click="handleEdit(record)">编辑</a-button>
<el-table-column label="操作"> <a-button v-perm="'sys:example:delete'" danger @click="handleDelete(record.id)">删除</a-button>
<template #default="{ row }">
<el-button v-perm="'sys:example:update'" @click="handleEdit(row)">编辑</el-button>
<el-button v-perm="'sys:example:delete'" type="danger" @click="handleDelete(row.id)">删除</el-button>
</template> </template>
</el-table-column> </template>
</el-table> </a-table>
<!-- 分页(后端固定返回 records / total / current / size / pages) --> <!-- 分页(后端固定返回 records / total / current / size / pages) -->
<el-pagination v-model:current-page="pageNo" v-model:page-size="pageSize" <a-pagination v-model:current="pageNo" v-model:page-size="pageSize"
:total="total" @change="fetchData" /> :total="total" @change="fetchData" />
</template> </template>
@@ -231,10 +228,8 @@ src/utils/http/axios/
|-------|------|-----------| |-------|------|-----------|
| userStore | `user.ts` | Token、用户信息、权限列表、登录/登出 | | userStore | `user.ts` | Token、用户信息、权限列表、登录/登出 |
| asyncRouteStore | `asyncRoute.ts` | 动态路由、keep-alive 缓存列表 | | asyncRouteStore | `asyncRoute.ts` | 动态路由、keep-alive 缓存列表 |
| projectSettingStore | `projectSetting.ts` | 项目配置(主题、布局等) | | projectSettingStore | `projectSetting.ts` | 项目配置(导航/顶栏主题、布局等) |
| tabsViewStore | `tabsView.ts` | 标签页导航状态 | | tabsViewStore | `tabsView.ts` | 标签页导航状态 |
| designSettingStore | `designSetting.ts` | 主题设计配置 |
| ossConfigStore | `ossConfig.ts` | 云存储配置 |
| lockscreenStore | `lockscreen.ts` | 锁屏状态 | | lockscreenStore | `lockscreen.ts` | 锁屏状态 |
**userStore 核心流程:** **userStore 核心流程:**
@@ -318,10 +313,10 @@ src/utils/http/axios/
```vue ```vue
<!-- v-perm:单权限 --> <!-- v-perm:单权限 -->
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button> <a-button v-perm="'sys:user:add'" type="primary">新增</a-button>
<!-- v-perms:多权限(满足任一即可) --> <!-- v-perms:多权限(满足任一即可) -->
<el-button v-perms="['sys:user:add', 'sys:user:update']">操作</el-button> <a-button v-perms="['sys:user:add', 'sys:user:update']">操作</a-button>
``` ```
无权限时元素直接从 DOM 移除,而非仅隐藏。 无权限时元素直接从 DOM 移除,而非仅隐藏。
@@ -419,7 +414,7 @@ v-perm 指令读取 permissions 列表 → 控制元素显隐
| `routes/api.php` | `src/api/**/*.ts` | 路由 ↔ API 函数 | | `routes/api.php` | `src/api/**/*.ts` | 路由 ↔ API 函数 |
| 字典数据(`dict` 表) | `src/enums/` + 字典 Store | 枚举常量 | | 字典数据(`dict` 表) | `src/enums/` + 字典 Store | 枚举常量 |
| `#[Permission]` 注解 | `v-perm` 指令 | 权限码对应 | | `#[Permission]` 注解 | `v-perm` 指令 | 权限码对应 |
| 分页 `records/total/current/size/pages` | el-pagination 绑定 | 固定分页结构 | | 分页 `records/total/current/size/pages` | a-pagination 绑定 | 固定分页结构 |
## 层间通信规范 ## 层间通信规范
@@ -8,7 +8,7 @@
```text ```text
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ 浏览器(Vue3 + ElementPlus) │ │ 浏览器(Vue3 + AntDesign) │
│ Axios HTTP 请求 / Vite 开发代理 │ │ Axios HTTP 请求 / Vite 开发代理 │
└──────────────────────────────┬──────────────────────────────────┘ └──────────────────────────────┬──────────────────────────────────┘
│ │
@@ -71,7 +71,7 @@
### 1. 前后端分离 ### 1. 前后端分离
前端 Vue3 应用通过 HTTP API 调用后端 Laravel 服务,两者独立开发、独立部署。前端开发阶段通过 Vite 的 `proxy` 配置将 `/api` 请求代理到后端 `http://localhost:8000`,生产环境由 Nginx 统一反向代理。 前端 Vue3 应用通过 HTTP API 调用后端 Laravel 服务,两者独立开发、独立部署。前端开发阶段通过 Vite 的 `proxy` 配置将 `/api` 请求代理到后端 `http://admin.laravel13.antdvue`,生产环境由 Nginx 统一反向代理。
### 2. 分层架构 ### 2. 分层架构
@@ -243,7 +243,7 @@ static::updating(function ($model) {
```text ```text
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ 前端(Vue3 + ElementPlus) │ │ 前端(Vue3 + AntDesign) │
│ Pinia · Vue Router · Axios · TinyMce · ECharts │ │ Pinia · Vue Router · Axios · TinyMce · ECharts │
└──────────────────────────┬───────────────────────────────────┘ └──────────────────────────┬───────────────────────────────────┘
│ HTTP / JSON │ HTTP / JSON
@@ -43,7 +43,7 @@ location / {
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000', target: 'http://admin.laravel13.antdvue',
changeOrigin: true, changeOrigin: true,
}, },
}, },
@@ -371,14 +371,17 @@ Content-Type: application/json
// code === 401(ResultEnum.TIMEOUT) // code === 401(ResultEnum.TIMEOUT)
case ResultEnum.TIMEOUT: case ResultEnum.TIMEOUT:
if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return; if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return;
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', { Modal.warning({
type: 'warning', title: '提示',
}) content: '登录身份已失效,请重新登录!',
.then(() => { closable: false,
maskClosable: false,
okText: '确定',
onOk: () => {
storage.clear(); // 清除本地登录态 storage.clear(); // 清除本地登录态
window.location.href = PageEnum.BASE_LOGIN; // '/login' window.location.href = PageEnum.BASE_LOGIN; // '/login'
}) },
.catch(() => {}); });
break; break;
``` ```
@@ -288,10 +288,10 @@ class CaptchaService
```vue ```vue
<template> <template>
<el-form-item> <a-form-item>
<el-input v-model="form.code" placeholder="验证码" /> <a-input v-model:value="form.code" placeholder="验证码" />
<img :src="captchaImage" @click="refreshCaptcha" style="cursor: pointer;" /> <img :src="captchaImage" @click="refreshCaptcha" style="cursor: pointer;" />
</el-form-item> </a-form-item>
</template> </template>
<script setup> <script setup>
@@ -257,7 +257,7 @@ data.forEach((group) => {
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 批量保存网站配置(key 为配置项编码) # 批量保存网站配置(key 为配置项编码)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl -X POST http://localhost:8000/api/config/web/save \ curl -X POST http://admin.laravel13.antdvue/api/config/web/save \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \ -H "Authorization: Bearer <token>" \
-d '{ -d '{
+2 -2
View File
@@ -301,7 +301,7 @@ Controller
```ini ```ini
# 检查 .env 中的 CORS_ALLOW_ORIGIN 是否包含前端的域名 # 检查 .env 中的 CORS_ALLOW_ORIGIN 是否包含前端的域名
CORS_ALLOW_ORIGIN=http://localhost:8001 CORS_ALLOW_ORIGIN=http://localhost:8002
``` ```
改后执行 `php artisan config:clear` 并重启服务。 改后执行 `php artisan config:clear` 并重启服务。
@@ -353,7 +353,7 @@ php artisan config:cache
## 环境配置建议 ## 环境配置建议
```ini ```ini
# 开发环境:允许所有来源(前端 localhost:8001,后端 localhost:8000) # 开发环境:允许所有来源(前端 localhost:8002,后端 admin.laravel13.antdvue)
CORS_ALLOW_ORIGIN=* CORS_ALLOW_ORIGIN=*
# 测试环境:指定测试域名,提前验证跨域配置 # 测试环境:指定测试域名,提前验证跨域配置
@@ -64,7 +64,7 @@ php artisan route:list --path=user/add --json
# → "middleware": ["api", "auth.jwt", "tenant", "demo", "log.operation"] # → "middleware": ["api", "auth.jwt", "tenant", "demo", "log.operation"]
# ③ 拦截生效(携带有效 token 发起写操作,期望 HTTP 403) # ③ 拦截生效(携带有效 token 发起写操作,期望 HTTP 403)
curl -X POST http://localhost:8000/api/user/add \ curl -X POST http://admin.laravel13.antdvue/api/user/add \
-H "Authorization: Bearer {access_token}" \ -H "Authorization: Bearer {access_token}" \
-H "Accept: application/json" \ -H "Accept: application/json" \
-d '{"username":"demo"}' -d '{"username":"demo"}'
@@ -265,13 +265,15 @@ class GeneratorController extends Controller
前端在收到 403 响应时,需识别演示模式拦截并给出友好提示: 前端在收到 403 响应时,需识别演示模式拦截并给出友好提示:
```typescript ```typescript
import { message } from 'ant-design-vue';
// HTTP 响应拦截器 // HTTP 响应拦截器
axios.interceptors.response.use(response => { axios.interceptors.response.use(response => {
const { code, msg } = response.data; const { code, msg } = response.data;
// 演示模式拦截 // 演示模式拦截
if (code === 403 && msg === '演示模式下禁止操作') { if (code === 403 && msg === '演示模式下禁止操作') {
ElMessage.warning('当前为演示环境,不允许执行此操作'); message.warning('当前为演示环境,不允许执行此操作');
return Promise.reject(response.data); return Promise.reject(response.data);
} }
@@ -284,13 +286,13 @@ axios.interceptors.response.use(response => {
```vue ```vue
<template> <template>
<!-- 演示模式下禁用删除按钮 --> <!-- 演示模式下禁用删除按钮 -->
<el-button <a-button
type="danger" danger
:disabled="isDemoMode" :disabled="isDemoMode"
@click="handleDelete" @click="handleDelete"
> >
删除 删除
</el-button> </a-button>
</template> </template>
<script setup> <script setup>
@@ -210,14 +210,15 @@ export function getDictItemByCode(code) {
```vue ```vue
<template> <template>
<el-select v-model="formData.status" placeholder="请选择状态"> <a-select v-model:value="formData.status" placeholder="请选择状态" style="width: 100%">
<el-option <a-select-option
v-for="item in statusOptions" v-for="item in statusOptions"
:key="item.value" :key="item.value"
:label="item.name"
:value="item.value" :value="item.value"
/> >
</el-select> {{ item.name }}
</a-select-option>
</a-select>
</template> </template>
<script setup> <script setup>
@@ -246,9 +247,9 @@ const [genderOptions, statusOptions] = await Promise.all([
```vue ```vue
<template> <template>
<el-tag :type="row.status === 1 ? 'success' : 'danger'"> <a-tag :color="row.status === 1 ? 'success' : 'error'">
{{ row.statusText }} {{ row.statusText }}
</el-tag> </a-tag>
</template> </template>
``` ```
+6 -5
View File
@@ -252,14 +252,15 @@ onMounted(async () => {
```vue ```vue
<template> <template>
<el-select v-model="form.gender" placeholder="请选择性别"> <a-select v-model:value="form.gender" placeholder="请选择性别" style="width: 100%">
<el-option <a-select-option
v-for="item in genderOptions" v-for="item in genderOptions"
:key="item.value" :key="item.value"
:label="item.name"
:value="item.value" :value="item.value"
/> >
</el-select> {{ item.name }}
</a-select-option>
</a-select>
</template> </template>
``` ```
+9 -4
View File
@@ -281,12 +281,17 @@ if (token) {
// code === 401(ResultEnum.TIMEOUT) // code === 401(ResultEnum.TIMEOUT)
case ResultEnum.TIMEOUT: case ResultEnum.TIMEOUT:
if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return; if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return;
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', { type: 'warning' }) Modal.warning({
.then(() => { title: '提示',
content: '登录身份已失效,请重新登录!',
closable: false,
maskClosable: false,
okText: '确定',
onOk: () => {
storage.clear(); storage.clear();
window.location.href = PageEnum.BASE_LOGIN; // '/login' window.location.href = PageEnum.BASE_LOGIN; // '/login'
}) },
.catch(() => {}); });
break; break;
``` ```
+2 -2
View File
@@ -336,10 +336,10 @@ class UserController extends Controller
```vue ```vue
<template> <template>
<!-- 单权限:拥有 sys:user:add 才显示 --> <!-- 单权限:拥有 sys:user:add 才显示 -->
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button> <a-button v-perm="'sys:user:add'" type="primary">新增</a-button>
<!-- 多权限:拥有任一权限即显示 --> <!-- 多权限:拥有任一权限即显示 -->
<el-button v-perms="['sys:user:add', 'sys:user:update']">操作</el-button> <a-button v-perms="['sys:user:add', 'sys:user:update']">操作</a-button>
<!-- 无权限的按钮直接从 DOM 移除,而非仅隐藏 --> <!-- 无权限的按钮直接从 DOM 移除,而非仅隐藏 -->
</template> </template>
+11 -11
View File
@@ -44,7 +44,7 @@ Windows 环境推荐使用 Laragon 集成安装(含 PHP / MySQL / Redis),
```bash ```bash
# 进入项目目录(以实际解压路径为准) # 进入项目目录(以实际解压路径为准)
cd RXThinkCMF_Laravel13_EleVue cd RXThinkCMF_Laravel13_AntdVue
``` ```
:::tip 温馨提示 :::tip 温馨提示
@@ -95,7 +95,7 @@ APP_DEBUG=true
DB_CONNECTION=mysql # 数据库类型(mysql / pgsql / sqlsrv / sqlite) DB_CONNECTION=mysql # 数据库类型(mysql / pgsql / sqlsrv / sqlite)
DB_HOST=127.0.0.1 # 数据库地址 DB_HOST=127.0.0.1 # 数据库地址
DB_PORT=3306 # 端口 DB_PORT=3306 # 端口
DB_DATABASE=rxthinkcmf.laravel13.elevue # 数据库名(需提前创建) DB_DATABASE=rxthinkcmf.laravel13.antdvue # 数据库名(需提前创建)
DB_USERNAME=root # 用户名 DB_USERNAME=root # 用户名
DB_PASSWORD=root # 密码 DB_PASSWORD=root # 密码
DB_PREFIX=laravel_ # 表前缀(数据库脚本使用 laravel_ 前缀,必须保留) DB_PREFIX=laravel_ # 表前缀(数据库脚本使用 laravel_ 前缀,必须保留)
@@ -144,16 +144,16 @@ mysql -uroot -p
# 在 MySQL 命令行中执行(创建项目数据库): # 在 MySQL 命令行中执行(创建项目数据库):
# CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符 # CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
# COLLATE utf8mb4_general_ci → 不区分大小写的排序规则 # COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
CREATE DATABASE `rxthinkcmf.laravel13.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE DATABASE `rxthinkcmf.laravel13.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 第 2 步:导入数据库脚本(约 226MB,含 25 张业务表 + 初始数据) # 第 2 步:导入数据库脚本(约 226MB,含 25 张业务表 + 初始数据)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
mysql -uroot -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -uroot -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
:::tip 温馨提示 :::tip 温馨提示
- 脚本位于 `document/mysql/rxthinkcmf.laravel13.elevue.sql`(五种数据库各有专用脚本,按 `document/<数据库类型>/` 分组),导入耗时约 1~3 分钟; - 脚本位于 `document/mysql/rxthinkcmf.laravel13.antdvue.sql`(五种数据库各有专用脚本,按 `document/<数据库类型>/` 分组),导入耗时约 1~3 分钟;
- 使用 [2.8 Docker 快速体验](/zh/quickstart/docker.html) 时无需手动导入,首次启动自动完成; - 使用 [2.8 Docker 快速体验](/zh/quickstart/docker.html) 时无需手动导入,首次启动自动完成;
- 详细的多数据库初始化步骤请参考 [2.5 数据库初始化](/zh/quickstart/database-init.html)。 - 详细的多数据库初始化步骤请参考 [2.5 数据库初始化](/zh/quickstart/database-init.html)。
::: :::
@@ -191,14 +191,14 @@ INFO Server running on [http://127.0.0.1:8000].
``` ```
:::tip 温馨提示 :::tip 温馨提示
打开浏览器访问 `http://localhost:8000/` 可验证后端服务是否正常启动;健康检查接口为 `http://localhost:8000/up`。 打开浏览器访问 `http://admin.laravel13.antdvue/` 可验证后端服务是否正常启动;健康检查接口为 `http://admin.laravel13.antdvue/up`。
::: :::
## 第四步:前端启动 ## 第四步:前端启动
### 4.1 安装前端依赖 ### 4.1 安装前端依赖
前端 EleVue 管理后台位于本仓库的 `ui/` 目录(Vue3 + ElementPlus): 前端 AntdVue 管理后台位于本仓库的 `ui/` 目录(Vue3 + AntDesign):
```bash ```bash
# 进入前端项目目录 # 进入前端项目目录
@@ -234,12 +234,12 @@ pnpm dev
```text ```text
VITE v3.x.x ready in xxx ms VITE v3.x.x ready in xxx ms
➜ Local: http://localhost:8001/ ➜ Local: http://localhost:8002/
``` ```
### 4.3 访问系统 ### 4.3 访问系统
打开浏览器访问 `http://localhost:8001`,使用默认账号登录: 打开浏览器访问 `http://localhost:8002`,使用默认账号登录:
```text ```text
账号:admin 账号:admin
@@ -247,7 +247,7 @@ pnpm dev
``` ```
:::tip 温馨提示 :::tip 温馨提示
前端默认运行在 `8001` 端口,后端默认 `8000` 端口。Vite 开发代理由 `ui/.env.development` 的 `VITE_PROXY` 决定(默认转发到 `http://admin.laravel13.elevue/api`;直连 `php artisan serve` 时改为 `http://localhost:8000/api`,改后重启生效)。登录后请立即修改默认密码。 前端默认运行在 `8002` 端口,后端默认 `8000` 端口。Vite 开发代理由 `ui/.env.development` 的 `VITE_PROXY` 决定(默认转发到 `http://admin.laravel13.antdvue/api`;直连 `php artisan serve` 时改为 `http://admin.laravel13.antdvue/api`,改后重启生效)。登录后请立即修改默认密码。
::: :::
## 第五步:功能验证 ## 第五步:功能验证
@@ -267,7 +267,7 @@ pnpm dev
也可直接用命令行验证接口链路(验证码接口同时验证 PHP → Redis 链路): 也可直接用命令行验证接口链路(验证码接口同时验证 PHP → Redis 链路):
```bash ```bash
curl http://localhost:8000/api/captcha curl http://admin.laravel13.antdvue/api/captcha
# → {"code":0,"ok":true,"msg":"操作成功","data":{...}} # → {"code":0,"ok":true,"msg":"操作成功","data":{...}}
``` ```
@@ -267,7 +267,7 @@ crontab -l
# /mo 1 → 每 1 分钟执行一次 # /mo 1 → 每 1 分钟执行一次
# /f → 强制覆盖同名任务 # /f → 强制覆盖同名任务
# ============================================================================= # =============================================================================
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\laragon\www\RXThinkCMF_Laravel13_EleVue && D:\laragon\bin\php\php-8.3\php.exe artisan job:run" /sc minute /mo 1 /f schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\laragon\www\RXThinkCMF_Laravel13_AntdVue && D:\laragon\bin\php\php-8.3\php.exe artisan job:run" /sc minute /mo 1 /f
# 或通过图形界面:Win+R → 输入 taskschd.msc → 回车 # 或通过图形界面:Win+R → 输入 taskschd.msc → 回车
``` ```
@@ -331,10 +331,10 @@ sudo systemctl start mysql
# 创建生产数据库 # 创建生产数据库
# CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符 # CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
# COLLATE utf8mb4_general_ci → 不区分大小写的排序规则 # COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
# 导入初始化数据(项目自带的 SQL 文件) # 导入初始化数据(项目自带的 SQL 文件)
mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
### PostgreSQL ### PostgreSQL
@@ -350,7 +350,7 @@ sudo apt install php8.3-pgsql
sudo systemctl restart php8.3-fpm sudo systemctl restart php8.3-fpm
# 创建数据库(-u postgres 以 postgres 用户执行) # 创建数据库(-u postgres 以 postgres 用户执行)
sudo -u postgres createdb rxthinkcmf.laravel13.elevue sudo -u postgres createdb rxthinkcmf.laravel13.antdvue
``` ```
### SQLite ### SQLite
@@ -16,25 +16,25 @@
# -proot → 数据库密码(生产环境建议用 -p 后不跟密码,交互式输入) # -proot → 数据库密码(生产环境建议用 -p 后不跟密码,交互式输入)
# rxthinkcmf... → 要备份的数据库名 # rxthinkcmf... → 要备份的数据库名
# > backup_*.sql → 输出到文件,$(date +%Y%m%d) 自动生成日期后缀 # > backup_*.sql → 输出到文件,$(date +%Y%m%d) 自动生成日期后缀
mysqldump -u root -proot rxthinkcmf.laravel13.elevue > backup_$(date +%Y%m%d).sql mysqldump -u root -proot rxthinkcmf.laravel13.antdvue > backup_$(date +%Y%m%d).sql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 压缩备份(推荐 — 可节省 70%+ 磁盘空间) # 压缩备份(推荐 — 可节省 70%+ 磁盘空间)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# | gzip → 将 mysqldump 输出通过管道传给 gzip 压缩 # | gzip → 将 mysqldump 输出通过管道传给 gzip 压缩
mysqldump -u root -proot rxthinkcmf.laravel13.elevue | gzip > backup_$(date +%Y%m%d).sql.gz mysqldump -u root -proot rxthinkcmf.laravel13.antdvue | gzip > backup_$(date +%Y%m%d).sql.gz
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 仅备份表结构(不含数据 — 用于对比表结构变更) # 仅备份表结构(不含数据 — 用于对比表结构变更)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# --no-data → 只导出 CREATE TABLE 语句,不导出数据 # --no-data → 只导出 CREATE TABLE 语句,不导出数据
mysqldump -u root -proot --no-data rxthinkcmf.laravel13.elevue > schema_$(date +%Y%m%d).sql mysqldump -u root -proot --no-data rxthinkcmf.laravel13.antdvue > schema_$(date +%Y%m%d).sql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 仅备份数据(不含表结构 — 用于数据迁移) # 仅备份数据(不含表结构 — 用于数据迁移)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# --no-create-info → 只导出 INSERT 语句,不导出 CREATE TABLE # --no-create-info → 只导出 INSERT 语句,不导出 CREATE TABLE
mysqldump -u root -proot --no-create-info rxthinkcmf.laravel13.elevue > data_$(date +%Y%m%d).sql mysqldump -u root -proot --no-create-info rxthinkcmf.laravel13.antdvue > data_$(date +%Y%m%d).sql
``` ```
:::info 大库提示 :::info 大库提示
@@ -56,7 +56,7 @@ mysqldump -u root -proot --no-create-info rxthinkcmf.laravel13.elevue > data_$(d
# 配置参数(按实际环境修改) # 配置参数(按实际环境修改)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
BACKUP_DIR="/backup/mysql" # 备份文件存放目录 BACKUP_DIR="/backup/mysql" # 备份文件存放目录
DB_NAME="rxthinkcmf.laravel13.elevue" # 要备份的数据库名 DB_NAME="rxthinkcmf.laravel13.antdvue" # 要备份的数据库名
DB_USER="root" # 数据库用户名 DB_USER="root" # 数据库用户名
DB_PASS="root" # 数据库密码 DB_PASS="root" # 数据库密码
DATE=$(date +%Y%m%d_%H%M%S) # 当前时间戳(年月日_时分秒) DATE=$(date +%Y%m%d_%H%M%S) # 当前时间戳(年月日_时分秒)
@@ -146,22 +146,22 @@ find $BACKUP_DIR -name "uploads_*.tar.gz" -mtime +30 -delete # 清理 30 天
# 从 SQL 文件恢复(全量覆盖) # 从 SQL 文件恢复(全量覆盖)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# < backup_*.sql → 将文件内容作为 mysql 命令的输入 # < backup_*.sql → 将文件内容作为 mysql 命令的输入
mysql -u root -proot rxthinkcmf.laravel13.elevue < backup_20260920.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < backup_20260920.sql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 从压缩文件恢复(gunzip 解压后传给 mysql) # 从压缩文件恢复(gunzip 解压后传给 mysql)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# gunzip < file.sql.gz → 解压并输出到标准输出 # gunzip < file.sql.gz → 解压并输出到标准输出
# | mysql → 通过管道传给 mysql 命令执行 # | mysql → 通过管道传给 mysql 命令执行
gunzip < backup_20260920.sql.gz | mysql -u root -proot rxthinkcmf.laravel13.elevue gunzip < backup_20260920.sql.gz | mysql -u root -proot rxthinkcmf.laravel13.antdvue
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 恢复到新数据库(安全起见 — 先验证再切换) # 恢复到新数据库(安全起见 — 先验证再切换)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 1. 创建临时验证数据库 # 1. 创建临时验证数据库
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue_restore\`;" mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue_restore\`;"
# 2. 恢复备份到临时库 # 2. 恢复备份到临时库
mysql -u root -proot rxthinkcmf.laravel13.elevue_restore < backup_20260920.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue_restore < backup_20260920.sql
# 3. 验证数据无误后,重命名数据库(需停机操作) # 3. 验证数据无误后,重命名数据库(需停机操作)
``` ```
@@ -205,7 +205,7 @@ tar -xzf /backup/uploads_20260920.tar.gz -C / www/api/public/uploads/article/
# ============================================================================= # =============================================================================
BACKUP_FILE=$1 # 第一个参数:备份文件路径 BACKUP_FILE=$1 # 第一个参数:备份文件路径
VERIFY_DB="rxthinkcmf.laravel13.elevue_verify" # 临时验证数据库名 VERIFY_DB="rxthinkcmf.laravel13.antdvue_verify" # 临时验证数据库名
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 创建验证数据库(先删除旧的,再创建新的) # 创建验证数据库(先删除旧的,再创建新的)
@@ -27,9 +27,9 @@
**数据库**: **数据库**:
- [ ] 安装 MySQL 8.0+(或 PostgreSQL / SQL Server / Oracle / SQLite) - [ ] 安装 MySQL 8.0+(或 PostgreSQL / SQL Server / Oracle / SQLite)
- [ ] 创建数据库:`CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;` - [ ] 创建数据库:`CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
- [ ] 创建数据库用户并授权 - [ ] 创建数据库用户并授权
- [ ] 导入 SQL 脚本:`mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql` - [ ] 导入 SQL 脚本:`mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql`
:::info 表结构来源 :::info 表结构来源
本项目表结构以 SQL 脚本为准(`document/mysql/`),`database/migrations/` 为空,不要依赖 `php artisan migrate` 建表。 本项目表结构以 SQL 脚本为准(`document/mysql/`),`database/migrations/` 为空,不要依赖 `php artisan migrate` 建表。
@@ -150,7 +150,7 @@ server {
cp -r /www/api /www/api_backup_$(date +%Y%m%d) cp -r /www/api /www/api_backup_$(date +%Y%m%d)
# 备份数据库(mysqldump 导出 SQL,$(date +%Y%m%d) 自动生成日期后缀) # 备份数据库(mysqldump 导出 SQL,$(date +%Y%m%d) 自动生成日期后缀)
mysqldump -u root -proot rxthinkcmf.laravel13.elevue > /backup/db_$(date +%Y%m%d).sql mysqldump -u root -proot rxthinkcmf.laravel13.antdvue > /backup/db_$(date +%Y%m%d).sql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 第 2 步:更新代码 # 第 2 步:更新代码
@@ -161,7 +161,7 @@ composer install --no-dev --optimize-autoloader # 安装依赖(排除开
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 第 3 步:更新数据库(如有迁移脚本) # 第 3 步:更新数据库(如有迁移脚本)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/migration.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/migration.sql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 第 4 步:清除并重建缓存 # 第 4 步:清除并重建缓存
@@ -210,7 +210,7 @@ sudo nginx -s reload
cp -r /www/api_backup_20260920/* /www/api/ cp -r /www/api_backup_20260920/* /www/api/
# 恢复数据库 # 恢复数据库
mysql -u root -proot rxthinkcmf.laravel13.elevue < /backup/db_20260920.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < /backup/db_20260920.sql
# 重启 PHP-FPM(清除 OPcache,加载旧代码) # 重启 PHP-FPM(清除 OPcache,加载旧代码)
sudo systemctl restart php8.3-fpm sudo systemctl restart php8.3-fpm
@@ -12,11 +12,11 @@
```text ```text
document/ document/
├── mysql/rxthinkcmf.laravel13.elevue.sql # MySQL 版本(约 216MB) ├── mysql/rxthinkcmf.laravel13.antdvue.sql # MySQL 版本(约 216MB)
├── postgresql/rxthinkcmf.laravel13.elevue.sql # PostgreSQL 版本 ├── postgresql/rxthinkcmf.laravel13.antdvue.sql # PostgreSQL 版本
├── sqlserver/rxthinkcmf.laravel13.elevue.sql # SQL Server 版本 ├── sqlserver/rxthinkcmf.laravel13.antdvue.sql # SQL Server 版本
├── oracle/rxthinkcmf.laravel13.elevue.sql # Oracle 版本 ├── oracle/rxthinkcmf.laravel13.antdvue.sql # Oracle 版本
└── sqlite/rxthinkcmf.laravel13.elevue.sql # SQLite 版本 └── sqlite/rxthinkcmf.laravel13.antdvue.sql # SQLite 版本
``` ```
各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入。表前缀统一为 `laravel_`(与 `.env` 中 `DB_PREFIX` 一致)。 各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入。表前缀统一为 `laravel_`(与 `.env` 中 `DB_PREFIX` 一致)。
@@ -32,7 +32,7 @@ document/
# -u root -proot → 用户名和密码 # -u root -proot → 用户名和密码
# rxthinkcmf... → 目标数据库名 # rxthinkcmf... → 目标数据库名
# < file.sql → 将 SQL 文件作为输入执行 # < file.sql → 将 SQL 文件作为输入执行
mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
### PostgreSQL ### PostgreSQL
@@ -44,7 +44,7 @@ mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.lar
# -U postgres → 连接用户 # -U postgres → 连接用户
# -d ... → 目标数据库 # -d ... → 目标数据库
# -f file.sql → 执行 SQL 文件 # -f file.sql → 执行 SQL 文件
psql -U postgres -d rxthinkcmf.laravel13.elevue -f document/postgresql/rxthinkcmf.laravel13.elevue.sql psql -U postgres -d rxthinkcmf.laravel13.antdvue -f document/postgresql/rxthinkcmf.laravel13.antdvue.sql
``` ```
### SQL Server ### SQL Server
@@ -58,7 +58,7 @@ psql -U postgres -d rxthinkcmf.laravel13.elevue -f document/postgresql/rxthinkcm
# -P root → 密码 # -P root → 密码
# -d ... → 目标数据库 # -d ... → 目标数据库
# -i file.sql → 执行 SQL 文件 # -i file.sql → 执行 SQL 文件
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.elevue -i document/sqlserver/rxthinkcmf.laravel13.elevue.sql sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.antdvue -i document/sqlserver/rxthinkcmf.laravel13.antdvue.sql
``` ```
### Oracle ### Oracle
@@ -69,7 +69,7 @@ sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.elevue -i document/sql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# root/root@localhost:1521/XE → 用户/密码@主机:端口/服务名 # root/root@localhost:1521/XE → 用户/密码@主机:端口/服务名
# @file.sql → 执行 SQL 文件 # @file.sql → 执行 SQL 文件
sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.laravel13.elevue.sql sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.laravel13.antdvue.sql
``` ```
### SQLite ### SQLite
@@ -80,7 +80,7 @@ sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.laravel13.elevue
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 数据库文件路径 → SQLite 数据库存储在单个文件中 # 数据库文件路径 → SQLite 数据库存储在单个文件中
# < file.sql → 将 SQL 文件作为输入执行 # < file.sql → 将 SQL 文件作为输入执行
sqlite3 database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.elevue.sql sqlite3 database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.antdvue.sql
``` ```
## 跨数据库迁移(CLI) ## 跨数据库迁移(CLI)
@@ -98,9 +98,9 @@ sqlite3 database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.elevue.s
# 目标库不存在时自动创建;迁到 PostgreSQL 需先在 php.ini 启用 extension=pdo_pgsql # 目标库不存在时自动创建;迁到 PostgreSQL 需先在 php.ini 启用 extension=pdo_pgsql
php artisan db:migrate \ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \ --dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=postgres --dst-password=123456 --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=postgres --dst-password=123456
``` ```
## 注意事项 ## 注意事项
@@ -55,7 +55,7 @@
## 文件结构 ## 文件结构
```text ```text
RXThinkCMF_Laravel13_EleVue/ RXThinkCMF_Laravel13_AntdVue/
├── docker/ ├── docker/
│ ├── Dockerfile # PHP-FPM 应用镜像定义 │ ├── Dockerfile # PHP-FPM 应用镜像定义
│ ├── nginx.conf # Nginx 站点配置 │ ├── nginx.conf # Nginx 站点配置
@@ -333,7 +333,7 @@ services:
- DB_CONNECTION=mysql - DB_CONNECTION=mysql
- DB_HOST=mysql - DB_HOST=mysql
- DB_PORT=3306 - DB_PORT=3306
- DB_DATABASE=${MYSQL_DATABASE:-rxthinkcmf.laravel13.elevue} - DB_DATABASE=${MYSQL_DATABASE:-rxthinkcmf.laravel13.antdvue}
- DB_USERNAME=root - DB_USERNAME=root
- DB_PASSWORD=${MYSQL_ROOT_PASSWORD} - DB_PASSWORD=${MYSQL_ROOT_PASSWORD}
@@ -347,7 +347,7 @@ services:
# JWT 密钥 / 文件域名 / 跨域(取自 --env-file) # JWT 密钥 / 文件域名 / 跨域(取自 --env-file)
- JWT_SECRET=${JWT_SECRET} - JWT_SECRET=${JWT_SECRET}
- FILE_DOMAIN_URL=${DOMAIN_URL:-http://localhost:8000} - FILE_DOMAIN_URL=${DOMAIN_URL:-http://admin.laravel13.antdvue}
- CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*} - CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*}
depends_on: depends_on:
@@ -371,11 +371,11 @@ services:
# - "3306:3306" # - "3306:3306"
environment: environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} # root 用户密码(>=16 位强密码) MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} # root 用户密码(>=16 位强密码)
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.laravel13.elevue} MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.laravel13.antdvue}
volumes: volumes:
- mysql_data:/var/lib/mysql # 数据持久化 - mysql_data:/var/lib/mysql # 数据持久化
# 初始化 SQL(首次启动自动执行,仅在数据卷为空时生效) # 初始化 SQL(首次启动自动执行,仅在数据卷为空时生效)
- ./document/mysql/rxthinkcmf.laravel13.elevue.sql:/docker-entrypoint-initdb.d/init.sql:ro - ./document/mysql/rxthinkcmf.laravel13.antdvue.sql:/docker-entrypoint-initdb.d/init.sql:ro
command: command:
- --character-set-server=utf8mb4 - --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci - --collation-server=utf8mb4_general_ci
@@ -442,7 +442,7 @@ REDIS_PORT=6379
# MySQL(首次启动自动导入数据库脚本) # MySQL(首次启动自动导入数据库脚本)
MYSQL_ROOT_PASSWORD=<16 位以上强密码> MYSQL_ROOT_PASSWORD=<16 位以上强密码>
MYSQL_DATABASE=rxthinkcmf.laravel13.elevue MYSQL_DATABASE=rxthinkcmf.laravel13.antdvue
# Redis(与数据库密码不同) # Redis(与数据库密码不同)
REDIS_PASSWORD=<强密码> REDIS_PASSWORD=<强密码>
@@ -497,7 +497,7 @@ docker compose logs -f app
# 6. 验证 # 6. 验证
curl https://your-domain/up curl https://your-domain/up
curl http://localhost:8000/api/captcha # 返回 code=0 即成功 curl http://admin.laravel13.antdvue/api/captcha # 返回 code=0 即成功
``` ```
### 首次初始化与数据迁移 ### 首次初始化与数据迁移
@@ -508,10 +508,10 @@ curl http://localhost:8000/api/captcha # 返回 code=0 即成功
```bash ```bash
# Linux / Git Bash # Linux / Git Bash
docker compose exec -T mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.elevue' < backup.sql docker compose exec -T mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.antdvue' < backup.sql
# Windows CMD(PowerShell 不支持 < 重定向,用 cmd /c 包裹) # Windows CMD(PowerShell 不支持 < 重定向,用 cmd /c 包裹)
cmd /c "docker compose exec -T mysql mysql -uroot -p<密码> rxthinkcmf.laravel13.elevue < backup.sql" cmd /c "docker compose exec -T mysql mysql -uroot -p<密码> rxthinkcmf.laravel13.antdvue < backup.sql"
``` ```
## 日常运维 ## 日常运维
@@ -545,16 +545,16 @@ docker compose logs -f mysql # 数据库日志(首次看导入进度
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
docker compose exec app bash # 进入 PHP-FPM 容器 docker compose exec app bash # 进入 PHP-FPM 容器
docker compose exec mysql bash # 进入 MySQL 容器 docker compose exec mysql bash # 进入 MySQL 容器
docker compose exec mysql mysql -uroot -p'<密码>' rxthinkcmf.laravel13.elevue docker compose exec mysql mysql -uroot -p'<密码>' rxthinkcmf.laravel13.antdvue
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 数据库操作 # 数据库操作
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 导出数据库备份 # 导出数据库备份
docker compose exec -T mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.elevue' > backup_$(date +%Y%m%d).sql docker compose exec -T mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.antdvue' > backup_$(date +%Y%m%d).sql
# 导入 SQL 文件 # 导入 SQL 文件
docker compose exec -T mysql mysql -uroot -p'<密码>' rxthinkcmf.laravel13.elevue < backup.sql docker compose exec -T mysql mysql -uroot -p'<密码>' rxthinkcmf.laravel13.antdvue < backup.sql
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Laravel Artisan 命令 # Laravel Artisan 命令
@@ -662,10 +662,10 @@ nginx:
```bash ```bash
# 数据库备份(定时任务执行) # 数据库备份(定时任务执行)
docker compose exec -T mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.elevue' > backup_$(date +%F).sql docker compose exec -T mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.antdvue' > backup_$(date +%F).sql
# 数据库恢复 # 数据库恢复
docker compose exec -T mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.elevue' < backup_2026-10-01.sql docker compose exec -T mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" rxthinkcmf.laravel13.antdvue' < backup_2026-10-01.sql
# 整卷备份(停机快照) # 整卷备份(停机快照)
docker compose down docker compose down
@@ -133,8 +133,8 @@ ALTER USER 'root'@'localhost' IDENTIFIED BY 'strong_password_here';
CREATE USER 'rxthinkcmf_app'@'localhost' IDENTIFIED BY 'app_password'; CREATE USER 'rxthinkcmf_app'@'localhost' IDENTIFIED BY 'app_password';
-- 授权应用用户只能访问项目数据库(最小权限原则) -- 授权应用用户只能访问项目数据库(最小权限原则)
-- rxthinkcmf.laravel13.elevue.* → 该项目数据库的所有表 -- rxthinkcmf.laravel13.antdvue.* → 该项目数据库的所有表
GRANT ALL PRIVILEGES ON `rxthinkcmf.laravel13.elevue`.* TO 'rxthinkcmf_app'@'localhost'; GRANT ALL PRIVILEGES ON `rxthinkcmf.laravel13.antdvue`.* TO 'rxthinkcmf_app'@'localhost';
FLUSH PRIVILEGES; -- 刷新权限使配置生效 FLUSH PRIVILEGES; -- 刷新权限使配置生效
-- 禁止远程 root 登录(只允许本地连接) -- 禁止远程 root 登录(只允许本地连接)
@@ -101,7 +101,7 @@ mysql -u root -p -e "SELECT user, host FROM mysql.user;"
# ON `数据库`.* → 指定数据库的所有表 # ON `数据库`.* → 指定数据库的所有表
# TO 'root'@'%' → 允许从任意主机连接的 root 用户 # TO 'root'@'%' → 允许从任意主机连接的 root 用户
# IDENTIFIED BY → 设置密码 # IDENTIFIED BY → 设置密码
mysql -u root -proot -e "GRANT ALL ON \`rxthinkcmf.laravel13.elevue\`.* TO 'root'@'%' IDENTIFIED BY 'root';" mysql -u root -proot -e "GRANT ALL ON \`rxthinkcmf.laravel13.antdvue\`.* TO 'root'@'%' IDENTIFIED BY 'root';"
mysql -u root -p -e "FLUSH PRIVILEGES;" # 刷新权限使配置生效 mysql -u root -p -e "FLUSH PRIVILEGES;" # 刷新权限使配置生效
``` ```
@@ -116,7 +116,7 @@ sudo systemctl status postgresql
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 测试连接(-U 指定用户,-d 指定数据库,-c 执行 SQL) # 测试连接(-U 指定用户,-d 指定数据库,-c 执行 SQL)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
psql -U postgres -d rxthinkcmf.laravel13.elevue -c "SELECT 1;" psql -U postgres -d rxthinkcmf.laravel13.antdvue -c "SELECT 1;"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 检查客户端认证配置(pg_hba.conf 控制哪些 IP 可以连接) # 检查客户端认证配置(pg_hba.conf 控制哪些 IP 可以连接)
@@ -173,14 +173,14 @@ php -m | grep pdo_sqlsrv # 查找 pdo_sqlsrv 扩展
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# MySQL:通过 mysql 命令导入 SQL 文件 # MySQL:通过 mysql 命令导入 SQL 文件
mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
# PostgreSQL:通过 psql 命令导入 SQL 文件 # PostgreSQL:通过 psql 命令导入 SQL 文件
# -U 指定用户,-d 指定数据库,-f 指定 SQL 文件 # -U 指定用户,-d 指定数据库,-f 指定 SQL 文件
psql -U postgres -d rxthinkcmf.laravel13.elevue -f document/postgresql/rxthinkcmf.laravel13.elevue.sql psql -U postgres -d rxthinkcmf.laravel13.antdvue -f document/postgresql/rxthinkcmf.laravel13.antdvue.sql
# SQLite:通过 sqlite3 命令导入 SQL 文件 # SQLite:通过 sqlite3 命令导入 SQL 文件
sqlite3 /www/api/database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.elevue.sql sqlite3 /www/api/database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.antdvue.sql
``` ```
:::info 本项目无 migration 建表 :::info 本项目无 migration 建表
@@ -286,7 +286,7 @@ sudo chown -R www-data:www-data /www/api/public/uploads/
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token # -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token
# <token> 替换为实际的 access_token 值 # <token> 替换为实际的 access_token 值
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/index/getUser curl -H "Authorization: Bearer <token>" http://admin.laravel13.antdvue/api/index/getUser
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 检查 JWT 密钥配置(缺失或过短会 fail-close,拒绝签发/校验令牌) # 检查 JWT 密钥配置(缺失或过短会 fail-close,拒绝签发/校验令牌)
@@ -301,13 +301,13 @@ grep "^JWT_SECRET" /www/api/.env
# 检查用户角色分配(用户是否分配了角色) # 检查用户角色分配(用户是否分配了角色)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# laravel_user_role 表存储用户与角色的关联关系 # laravel_user_role 表存储用户与角色的关联关系
mysql -u root -proot rxthinkcmf.laravel13.elevue -e "SELECT * FROM laravel_user_role WHERE user_id=1;" mysql -u root -proot rxthinkcmf.laravel13.antdvue -e "SELECT * FROM laravel_user_role WHERE user_id=1;"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 检查角色权限(角色是否分配了菜单权限) # 检查角色权限(角色是否分配了菜单权限)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# laravel_role_menu 表存储角色与菜单的关联关系 # laravel_role_menu 表存储角色与菜单的关联关系
mysql -u root -proot rxthinkcmf.laravel13.elevue -e "SELECT * FROM laravel_role_menu WHERE role_id=1;" mysql -u root -proot rxthinkcmf.laravel13.antdvue -e "SELECT * FROM laravel_role_menu WHERE role_id=1;"
``` ```
### 文件上传失败 ### 文件上传失败
@@ -431,7 +431,7 @@ cd /www/api && php artisan job:daemon --stop
# 测试后端路由是否正常(绕过前端,直接请求 API) # 测试后端路由是否正常(绕过前端,直接请求 API)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -H → 设置请求头(携带 JWT Token) # -H → 设置请求头(携带 JWT Token)
curl http://localhost:8000/api/example/page -H "Authorization: Bearer <token>" curl http://admin.laravel13.antdvue/api/example/page -H "Authorization: Bearer <token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 检查 Nginx 配置语法是否正确 # 检查 Nginx 配置语法是否正确
+9 -9
View File
@@ -14,7 +14,7 @@
## 项目根目录 ## 项目根目录
```text ```text
RXThinkCMF_Laravel13_EleVue/ RXThinkCMF_Laravel13_AntdVue/
├── app/ // 后端源码(见下方详解) ├── app/ // 后端源码(见下方详解)
├── bootstrap/ // 应用引导(app.php 注册中间件别名与健康检查路由) ├── bootstrap/ // 应用引导(app.php 注册中间件别名与健康检查路由)
├── config/ // 配置文件(18 个) ├── config/ // 配置文件(18 个)
@@ -36,7 +36,7 @@ RXThinkCMF_Laravel13_EleVue/
│ └── web.php // 欢迎页路由 │ └── web.php // 欢迎页路由
├── storage/ // 运行时产物(日志/缓存/会话/编译视图,需可写) ├── storage/ // 运行时产物(日志/缓存/会话/编译视图,需可写)
├── tests/ // PHPUnit 测试(Feature / Unit) ├── tests/ // PHPUnit 测试(Feature / Unit)
├── ui/ // ★ 前端 EleVue 管理后台(Vue3 + ElementPlus,见第 9 章) ├── ui/ // ★ 前端 AntdVue 管理后台(Vue3 + AntDesign,见第 9 章)
├── uploads/ // 上传文件目录 ├── uploads/ // 上传文件目录
├── vendor/ // Composer 依赖(不提交到版本控制) ├── vendor/ // Composer 依赖(不提交到版本控制)
├── artisan // Artisan 命令行入口 ├── artisan // Artisan 命令行入口
@@ -50,7 +50,7 @@ RXThinkCMF_Laravel13_EleVue/
``` ```
:::tip 前端代码位置 :::tip 前端代码位置
本仓库包含**后端服务**与 `ui/` 目录内的前端 EleVue 管理后台(Vue3 + ElementPlus);代码生成器会把生成的前端页面输出到 `ui/src/` 对应位置(见 [10.6 自定义模板开发](/zh/generator/templates.html))。前端工程结构详见 [第 9 章 前端开发](/zh/frontend/overview.html)。 本仓库包含**后端服务**与 `ui/` 目录内的前端 AntdVue 管理后台(Vue3 + AntDesign);代码生成器会把生成的前端页面输出到 `ui/src/` 对应位置(见 [10.6 自定义模板开发](/zh/generator/templates.html))。前端工程结构详见 [第 9 章 前端开发](/zh/frontend/overview.html)。
::: :::
## 后端目录 (app/) ## 后端目录 (app/)
@@ -192,7 +192,7 @@ database/
``` ```
:::warning 注意 :::warning 注意
本项目**未使用 Laravel 迁移建表**,数据库结构以 `document/<数据库类型>/rxthinkcmf.laravel13.elevue.sql` 脚本为准(25 张业务表,`laravel_` 前缀,五种数据库各有专用脚本)。`composer setup` 中的 `migrate --force` 仅初始化迁移记录表,不会创建业务表。详见 [2.5 数据库初始化](/zh/quickstart/database-init.html)。 本项目**未使用 Laravel 迁移建表**,数据库结构以 `document/<数据库类型>/rxthinkcmf.laravel13.antdvue.sql` 脚本为准(25 张业务表,`laravel_` 前缀,五种数据库各有专用脚本)。`composer setup` 中的 `migrate --force` 仅初始化迁移记录表,不会创建业务表。详见 [2.5 数据库初始化](/zh/quickstart/database-init.html)。
::: :::
## 代码生成模板 (resources/templates/) ## 代码生成模板 (resources/templates/)
@@ -240,15 +240,15 @@ docker/
document/ document/
├── .gitignore // 忽略规则(脚本等大文件不进版本库) ├── .gitignore // 忽略规则(脚本等大文件不进版本库)
├── mysql/ ├── mysql/
│ └── rxthinkcmf.laravel13.elevue.sql // ★ MySQL 初始化脚本(约 226MB,25 张业务表 + 初始数据) │ └── rxthinkcmf.laravel13.antdvue.sql // ★ MySQL 初始化脚本(约 226MB,25 张业务表 + 初始数据)
├── postgresql/ ├── postgresql/
│ └── rxthinkcmf.laravel13.elevue.sql // PostgreSQL 初始化脚本 │ └── rxthinkcmf.laravel13.antdvue.sql // PostgreSQL 初始化脚本
├── sqlserver/ ├── sqlserver/
│ └── rxthinkcmf.laravel13.elevue.sql // SQL Server 初始化脚本 │ └── rxthinkcmf.laravel13.antdvue.sql // SQL Server 初始化脚本
├── oracle/ ├── oracle/
│ └── rxthinkcmf.laravel13.elevue.sql // Oracle 初始化脚本 │ └── rxthinkcmf.laravel13.antdvue.sql // Oracle 初始化脚本
└── sqlite/ └── sqlite/
└── rxthinkcmf.laravel13.elevue.sql // SQLite 初始化脚本 └── rxthinkcmf.laravel13.antdvue.sql // SQLite 初始化脚本
``` ```
:::tip :::tip
@@ -119,7 +119,7 @@ export function exampleBatchDelete(data: any) {
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example'; import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
import { ElMessage, ElMessageBox } from 'element-plus'; import { message, Modal } from 'ant-design-vue';
const tableData = ref([]); const tableData = ref([]);
@@ -131,16 +131,21 @@ const fetchData = async () => {
// 删除 // 删除
const handleDelete = async (id) => { const handleDelete = async (id) => {
await ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }); Modal.confirm({
await exampleDelete(id); title: '提示',
ElMessage.success('删除成功'); content: '确认删除?',
fetchData(); onOk: async () => {
await exampleDelete(id);
message.success('删除成功');
fetchData();
},
});
}; };
// 批量删除 // 批量删除
const handleBatchDelete = async (ids) => { const handleBatchDelete = async (ids) => {
await exampleBatchDelete({ ids }); await exampleBatchDelete({ ids });
ElMessage.success('删除成功'); message.success('删除成功');
fetchData(); fetchData();
}; };
</script> </script>
@@ -1,20 +1,21 @@
# 9.2 目录结构详解 # 9.2 目录结构详解
:::tip 概述 :::tip 概述
本节详细介绍前端项目(独立 EleVue 仓库)的目录结构,帮助开发者快速定位代码和理解项目组织方式。 本节详细介绍前端项目(独立 AntdVue 仓库)的目录结构,帮助开发者快速定位代码和理解项目组织方式。
::: :::
```text ```text
ui/src/ ui/src/
├── api/ # API 接口层(按业务域分组) ├── api/ # API 接口层(按业务域分组)
│ ├── common/ # 公共接口(登录/菜单/用户信息/城市) │ ├── common/ # 公共接口(登录/菜单/用户信息/城市)
│ ├── system/ # 系统管理(用户/角色/菜单/部门/岗位/职级/日志/租户) │ ├── system/ # 系统管理(用户/角色/菜单/部门/岗位/职级/租户)
│ ├── content/ # 内容管理(文章/分类/链接) │ ├── content/ # 内容管理(文章/分类/链接)
│ ├── data/ # 数据管理(城市/配置/字典/公告/参数) │ ├── data/ # 数据管理(城市/配置/字典/公告/参数)
│ ├── dashboard/ # 仪表盘(控制台/消息) │ ├── dashboard/ # 仪表盘(控制台/消息)
│ ├── file/ # 文件管理(文件模板) │ ├── file/ # 文件管理(文件模板)
│ ├── logger/ # 日志管理(登录日志/操作日志)
│ ├── monitor/ # 监控管理(定时任务) │ ├── monitor/ # 监控管理(定时任务)
│ ├── region/ # 行政区划 │ ├── oauth2/ # OAuth2 授权
│ ├── setting/ # 设置(个人资料/网站配置) │ ├── setting/ # 设置(个人资料/网站配置)
│ └── tool/ # 工具(示例/代码生成器) │ └── tool/ # 工具(示例/代码生成器)
├── components/ # 公共组件库(20+ 个) ├── components/ # 公共组件库(20+ 个)
@@ -30,7 +31,10 @@ ui/src/
│ ├── Qrcode/ # 二维码 │ ├── Qrcode/ # 二维码
│ └── ... │ └── ...
├── directives/ # 自定义指令 ├── directives/ # 自定义指令
│ └── permission.ts # v-perm / v-perms 权限指令 │ ├── permission.ts # v-perm / v-perms 权限指令
│ ├── clickOutside.ts # 点击元素外部检测
│ ├── dragModal.ts # 弹窗拖拽
│ └── scrollBar.ts # 滚动条控制
├── enums/ # 枚举常量 ├── enums/ # 枚举常量
│ ├── httpEnum.ts # HTTP 枚举 │ ├── httpEnum.ts # HTTP 枚举
│ └── pageEnum.ts # 页面枚举 │ └── pageEnum.ts # 页面枚举
@@ -50,11 +54,10 @@ ui/src/
│ ├── user.ts # 用户 Store(Token/用户信息/权限) │ ├── user.ts # 用户 Store(Token/用户信息/权限)
│ ├── asyncRoute.ts # 动态路由 Store │ ├── asyncRoute.ts # 动态路由 Store
│ ├── tabsView.ts # 标签页 Store │ ├── tabsView.ts # 标签页 Store
│ ├── projectSetting.ts # 项目配置 Store │ ├── projectSetting.ts # 项目配置 Store(导航/顶栏主题等)
│ ├── designSetting.ts # 主题设计 Store
│ ├── lockscreen.ts # 锁屏 Store │ ├── lockscreen.ts # 锁屏 Store
│ └── ossConfig.ts # 云存储配置 Store │ └── index.ts # Store 入口(setupStore 挂载)
├── styles/ # 全局样式(Tailwind / 主题变量) ├── styles/ # 全局样式(WindiCSS / Less 主题变量)
├── utils/ # 工具函数 ├── utils/ # 工具函数
│ ├── http/axios/ # HTTP 请求封装(拦截器) │ ├── http/axios/ # HTTP 请求封装(拦截器)
│ └── Storage.ts # 本地存储封装 │ └── Storage.ts # 本地存储封装
@@ -65,9 +68,12 @@ ui/src/
├── content/ # 内容管理页面 ├── content/ # 内容管理页面
├── data/ # 数据管理页面 ├── data/ # 数据管理页面
├── monitor/ # 监控管理页面 ├── monitor/ # 监控管理页面
├── logger/ # 日志管理页面
├── oauth2/ # OAuth2 授权页面
├── file/ # 文件管理页面 ├── file/ # 文件管理页面
├── setting/ # 设置页面(个人资料等) ├── setting/ # 设置页面(个人资料等)
├── tool/ # 工具页面(示例/代码生成器) ├── tool/ # 工具页面(示例/代码生成器)
├── about/ # 关于页面
├── iframe/ # 内嵌 iframe 页面 ├── iframe/ # 内嵌 iframe 页面
├── redirect/ # 重定向页面 ├── redirect/ # 重定向页面
└── exception/ # 异常页面(403/404/500) └── exception/ # 异常页面(403/404/500)
@@ -14,7 +14,7 @@
:row-key="(row) => row.id" :row-key="(row) => row.id"
ref="tableRef" ref="tableRef"
:actionColumn="actionColumn" :actionColumn="actionColumn"
@selection-change="onSelectionChange" :row-selection="{ onChange: onSelectionChange }"
/> />
</template> </template>
@@ -36,7 +36,7 @@ const actionColumn = {
``` ```
:::tip 分页字段约定 :::tip 分页字段约定
本项目 `BasicTable` 通过 `:request` 传入分页请求函数(没有 `useTable` hook),内部绑定后端固定分页结构 `data.records` / `data.total`。`statusText` 等 `*Text` 字段由后端 `serializeMaps` 自动补全,直接作为列 `field` 使用即可。 本项目 `BasicTable` 通过 `:request` 传入分页请求函数(没有 `useTable` hook),内部绑定后端固定分页结构 `data.records` / `data.total`。`statusText` 等 `*Text` 字段由后端 `serializeMaps` 自动补全,直接作为列 `dataIndex` 使用即可。
::: :::
### 列定义常用属性 ### 列定义常用属性
@@ -44,10 +44,11 @@ const actionColumn = {
| 属性 | 说明 | 示例 | | 属性 | 说明 | 示例 |
|------|------|------| |------|------|------|
| `title` | 列标题 | `'创建时间'` | | `title` | 列标题 | `'创建时间'` |
| `field` | 字段名(camelCase,与响应一致) | `'createTime'` | | `dataIndex` | 数据字段名(camelCase,与响应一致) | `'createTime'` |
| `key` | 列标识(`#bodyCell` 插槽按 `column.key` 匹配) | `'action'` |
| `width` | 列宽 | `180` | | `width` | 列宽 | `180` |
| `formatter` | 自定义格式化 | `(v) => v ?? '-'` | | `customRender` | 自定义渲染函数(参数 `{ record }`) | `({ record }) => h(Tag, ...)` |
| `slots` | 自定义单元格插槽 | `'statusSlot'` | | `slots` | 自定义单元格插槽 | `{ customRender: 'statusSlot' }` |
## Page 页面容器 ## Page 页面容器
@@ -10,10 +10,8 @@
ui/ ui/
├── .env # 所有环境通用配置(端口、标题) ├── .env # 所有环境通用配置(端口、标题)
├── .env.development # 开发环境(pnpm dev,默认 mode) ├── .env.development # 开发环境(pnpm dev,默认 mode)
├── .env.production # 生产构建(pnpm build) ├── .env.production # 生产构建(pnpm build / build:prod)
├── .env.test # 测试构建(pnpm build:test) └── .env.test # 测试构建(pnpm build:test)
├── .env.uat # UAT 构建(pnpm build:uat)
└── .env.dev # dev 模式构建(pnpm build:dev)
``` ```
**加载优先级:** `.env.{mode}` > `.env`(Vite 标准加载规则,可用 `.env.{mode}.local` 做本地覆盖) **加载优先级:** `.env.{mode}` > `.env`(Vite 标准加载规则,可用 `.env.{mode}.local` 做本地覆盖)
@@ -24,7 +22,7 @@ ui/
```bash ```bash
# 开发服务器端口 # 开发服务器端口
VITE_PORT = 8001 VITE_PORT = 8002
# 公共基础路径(通常为 /) # 公共基础路径(通常为 /)
VITE_PUBLIC_PATH = / VITE_PUBLIC_PATH = /
@@ -36,7 +34,7 @@ VITE_USE_MOCK = false
VITE_DROP_CONSOLE = true VITE_DROP_CONSOLE = true
# 开发代理(JSON 数组格式:[["前缀","目标地址"]]) # 开发代理(JSON 数组格式:[["前缀","目标地址"]])
VITE_PROXY=[["/api","http://admin.laravel13.elevue/api"]] VITE_PROXY=[["/api","http://admin.laravel13.antdvue/api"]]
# API 接口基础路径(留空,由代理处理) # API 接口基础路径(留空,由代理处理)
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
@@ -57,7 +55,7 @@ VITE_GLOB_API_URL_PREFIX = /api
``` ```
:::tip 代理目标地址 :::tip 代理目标地址
`VITE_PROXY` 当前默认指向 Laragon 虚拟主机 `http://admin.laravel13.elevue/api`;使用 `php artisan serve` 时改为直连 `http://localhost:8000/api`(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html))。修改后需重启 `pnpm dev`。 `VITE_PROXY` 当前默认指向 Laragon 虚拟主机 `http://admin.laravel13.antdvue/api`;使用 `php artisan serve` 时改为直连 `http://admin.laravel13.antdvue/api`(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html))。修改后需重启 `pnpm dev`。
::: :::
## 生产环境配置 ## 生产环境配置
@@ -99,7 +97,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
| 环境 | 值 | 说明 | | 环境 | 值 | 说明 |
|------|-----|------| |------|-----|------|
| 开发 | `8001` | 前端开发服务器端口 | | 开发 | `8002` | 前端开发服务器端口 |
| 生产 | 不生效 | 生产环境由 Nginx 提供服务 | | 生产 | 不生效 | 生产环境由 Nginx 提供服务 |
### VITE_PUBLIC_PATH — 公共基础路径 ### VITE_PUBLIC_PATH — 公共基础路径
@@ -121,10 +119,10 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
```bash ```bash
# 当前 .env.development 中的值(Laragon 虚拟主机) # 当前 .env.development 中的值(Laragon 虚拟主机)
VITE_PROXY=[["/api","http://admin.laravel13.elevue/api"]] VITE_PROXY=[["/api","http://admin.laravel13.antdvue/api"]]
# 直连 artisan serve 时的写法 # 直连 artisan serve 时的写法
VITE_PROXY=[["/api","http://localhost:8000/api"]] VITE_PROXY=[["/api","http://admin.laravel13.antdvue/api"]]
``` ```
**格式:** JSON 数组,每项为 `[前缀, 目标地址]` **格式:** JSON 数组,每项为 `[前缀, 目标地址]`
@@ -134,7 +132,7 @@ VITE_PROXY=[["/api","http://localhost:8000/api"]]
**多代理示例:** **多代理示例:**
```bash ```bash
VITE_PROXY = [["/api","http://localhost:8000/api"],["/uploads","http://localhost:8000/uploads"]] VITE_PROXY = [["/api","http://admin.laravel13.antdvue/api"],["/uploads","http://admin.laravel13.antdvue/uploads"]]
``` ```
### VITE_GLOB_API_URL_PREFIX — API 前缀 ### VITE_GLOB_API_URL_PREFIX — API 前缀
@@ -246,7 +244,7 @@ export function createProxy(list: ProxyList = []) {
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000/api', // 目标地址(VITE_PROXY 驱动,含 /api 前缀) target: 'http://admin.laravel13.antdvue/api', // 目标地址(VITE_PROXY 驱动,含 /api 前缀)
changeOrigin: true, // 修改请求头 Origin changeOrigin: true, // 修改请求头 Origin
ws: true, // 支持 WebSocket ws: true, // 支持 WebSocket
rewrite: (path) => path.replace(new RegExp('^/api'), ''), // 去掉前缀,由目标地址补回 rewrite: (path) => path.replace(new RegExp('^/api'), ''), // 去掉前缀,由目标地址补回
@@ -262,7 +260,7 @@ server: {
│ │
▼ Vite proxy ▼ Vite proxy
│ │
▼ 转发到: http://localhost:8000/api/user/page ▼ 转发到: http://admin.laravel13.antdvue/api/user/page
``` ```
## 构建命令 ## 构建命令
@@ -294,7 +292,7 @@ ui/dist/
│ ├── index-xxx.js # 主 JS(带 hash) │ ├── index-xxx.js # 主 JS(带 hash)
│ ├── index-xxx.css # 主 CSS(带 hash) │ ├── index-xxx.css # 主 CSS(带 hash)
│ ├── vue-xxx.js # Vue 相关(分包) │ ├── vue-xxx.js # Vue 相关(分包)
│ └── element-xxx.js # Element Plus(分包) │ └── antd-xxx.js # AntDesign Vue(分包)
├── favicon.ico ├── favicon.ico
└── static/ # 静态资源 └── static/ # 静态资源
``` ```
@@ -339,22 +337,19 @@ server {
## 多环境配置 ## 多环境配置
### 测试 / UAT 环境(项目实际提供) ### 测试环境(项目实际提供)
```bash ```bash
# .env.test(pnpm build:test) # .env.test(pnpm build:test)
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api
VITE_DROP_CONSOLE = true VITE_DROP_CONSOLE = true
# .env.uat(pnpm build:uat)同结构,按需调整
``` ```
构建命令(见 `ui/package.json` scripts): 构建命令(见 `ui/package.json` scripts):
```bash ```bash
pnpm build:test # 测试环境构建 pnpm build:test # 测试环境构建
pnpm build:uat # UAT 环境构建
pnpm build:prod # 生产环境构建 pnpm build:prod # 生产环境构建
``` ```
@@ -362,10 +357,10 @@ pnpm build:prod # 生产环境构建
| 变量 | 开发 | 测试 | 生产 | | 变量 | 开发 | 测试 | 生产 |
|------|------|------|------| |------|------|------|------|
| `VITE_PORT` | 8001 | - | - | | `VITE_PORT` | 8002 | - | - |
| `VITE_GLOB_API_URL` | 留空 | 留空 | 留空 | | `VITE_GLOB_API_URL` | 留空 | 留空 | 留空 |
| `VITE_GLOB_API_URL_PREFIX` | /api | /api | /api | | `VITE_GLOB_API_URL_PREFIX` | /api | /api | /api |
| `VITE_PROXY` | `[["/api","http://admin.laravel13.elevue/api"]]` | - | - | | `VITE_PROXY` | `[["/api","http://admin.laravel13.antdvue/api"]]` | - | - |
| `VITE_GLOB_IMG_URL` | 留空 | 留空 | 留空(按需配置 CDN) | | `VITE_GLOB_IMG_URL` | 留空 | 留空 | 留空(按需配置 CDN) |
| `VITE_DROP_CONSOLE` | true | true | true | | `VITE_DROP_CONSOLE` | true | true | true |
| `VITE_USE_MOCK` | false | false | false | | `VITE_USE_MOCK` | false | false | false |
@@ -376,7 +371,7 @@ pnpm build:prod # 生产环境构建
**原因:** 代理配置错误或后端未启动。 **原因:** 代理配置错误或后端未启动。
**解决:** 检查 `.env.development` 中 `VITE_PROXY` 的目标地址是否正确,`curl http://localhost:8000/api/captcha` 验证后端;改完配置重启 `pnpm dev`。 **解决:** 检查 `.env.development` 中 `VITE_PROXY` 的目标地址是否正确,`curl http://admin.laravel13.antdvue/api/captcha` 验证后端;改完配置重启 `pnpm dev`。
### 问题 2:生产环境 API 请求跨域 ### 问题 2:生产环境 API 请求跨域
@@ -8,7 +8,7 @@
```vue ```vue
<template> <template>
<el-button @click="handleImport">导入</el-button> <a-button @click="handleImport">导入</a-button>
<ImportExcel @success="onImportSuccess" /> <ImportExcel @success="onImportSuccess" />
</template> </template>
@@ -24,32 +24,38 @@ const onImportSuccess = (data) => {
:::tip 前端解析 vs 后端导入 :::tip 前端解析 vs 后端导入
- **前端解析(本组件)**:`xlsx` 库本地解析,适合导入前预览、无业务校验的场景; - **前端解析(本组件)**:`xlsx` 库本地解析,适合导入前预览、无业务校验的场景;
- **后端导入(推荐)**:`el-upload` 直传 `/api/{module}/import`,由 Service 逐行 `add()`(字典反查、唯一校验、事务隔离、错误行明细),见 [7.4 数据导入导出](/zh/tools/excel.html)。 - **后端导入(推荐)**:`a-upload` 直传 `/api/{module}/import`,由 Service 逐行 `add()`(字典反查、唯一校验、事务隔离、错误行明细),见 [7.4 数据导入导出](/zh/tools/excel.html)。
::: :::
### 后端导入对接 ### 后端导入对接
```vue ```vue
<template> <template>
<el-upload <a-upload
:action="'/api/example/import'" :action="'/api/example/import'"
:headers="{ Authorization: `Bearer ${token}` }" :headers="{ Authorization: `Bearer ${token}` }"
:on-success="handleImportSuccess" :show-upload-list="false"
accept=".xls,.xlsx" accept=".xls,.xlsx"
@change="handleChange"
> >
<el-button type="primary">导入</el-button> <a-button type="primary">导入</a-button>
</el-upload> </a-upload>
</template> </template>
<script setup> <script setup>
const handleImportSuccess = (response) => { import { message } from 'ant-design-vue';
if (response.code === 0) {
const { count, errors } = response.data; const handleChange = ({ file }) => {
if (errors.length === 0) { if (file.status === 'done') {
ElMessage.success(`成功导入 ${count} 条数据`); const response = file.response;
} else { if (response.code === 0) {
ElMessage.warning(`成功 ${count} 条,失败 ${errors.length} 条`); const { count, errors } = response.data;
// errors: [{ row: 3, name: '...', reason: '...' }] if (errors.length === 0) {
message.success(`成功导入 ${count} 条数据`);
} else {
message.warning(`成功 ${count} 条,失败 ${errors.length} 条`);
// errors: [{ row: 3, name: '...', reason: '...' }]
}
} }
} }
}; };
@@ -17,13 +17,13 @@ import { BasicForm, useForm } from '@/components/Form';
const [registerForm] = useForm({ const [registerForm] = useForm({
schemas: [ schemas: [
{ {
field: 'name', name: 'name',
label: '名称', label: '名称',
component: 'Input', component: 'Input',
required: true, rules: [{ required: true, message: '请输入名称', trigger: 'blur' }],
}, },
{ {
field: 'status', name: 'status',
label: '状态', label: '状态',
component: 'Select', component: 'Select',
componentProps: { componentProps: {
@@ -39,7 +39,7 @@ const [registerForm] = useForm({
``` ```
:::tip 前端校验与后端 FormRequest 的关系 :::tip 前端校验与后端 FormRequest 的关系
`schemas` 的 `required`/校验规则是**体验层**(即时提示);后端 `Http/Requests/*.php` 的 `rules()` 才是**最终防线**。两边规则应保持一致(如 max 长度、in 枚举值),以后端为准。 `schemas` 的 `rules` 校验规则是**体验层**(即时提示);后端 `Http/Requests/*.php` 的 `rules()` 才是**最终防线**。两边规则应保持一致(如 max 长度、in 枚举值),以后端为准。
::: :::
## Modal 弹窗组件 ## Modal 弹窗组件
@@ -45,11 +45,11 @@ if (code === 401 || res.status === 401) {
if (code === 403 || res.status === 403) { if (code === 403 || res.status === 403) {
// 权限不足 / 租户异常 / 演示模式拦截 // 权限不足 / 租户异常 / 演示模式拦截
ElMessage.error(msg || '无访问权限'); message.error(msg || '无访问权限');
} }
// 其他业务错误,显示提示 // 其他业务错误,显示提示
ElMessage.error(msg || '操作失败'); message.error(msg || '操作失败');
``` ```
:::tip 响应处理规则 :::tip 响应处理规则
@@ -66,12 +66,17 @@ ElMessage.error(msg || '操作失败');
// code === 401(ResultEnum.TIMEOUT) // code === 401(ResultEnum.TIMEOUT)
case ResultEnum.TIMEOUT: case ResultEnum.TIMEOUT:
if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return; if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return;
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', { type: 'warning' }) Modal.warning({
.then(() => { title: '提示',
content: '登录身份已失效,请重新登录!',
closable: false,
maskClosable: false,
okText: '确定',
onOk: () => {
storage.clear(); storage.clear();
window.location.href = PageEnum.BASE_LOGIN; // '/login' window.location.href = PageEnum.BASE_LOGIN; // '/login'
}) },
.catch(() => {}); });
break; break;
``` ```
@@ -19,19 +19,22 @@
```vue ```vue
<!-- src/views/login/index.vue --> <!-- src/views/login/index.vue -->
<template> <template>
<el-form :model="loginForm" @submit.prevent="handleLogin"> <a-form :model="loginForm">
<el-form-item> <a-form-item name="username">
<el-input v-model="loginForm.username" placeholder="用户名" /> <a-input v-model:value="loginForm.username" placeholder="用户名" />
</el-form-item> </a-form-item>
<el-form-item> <a-form-item name="password">
<el-input v-model="loginForm.password" type="password" placeholder="密码" /> <a-input-password v-model:value="loginForm.password" placeholder="密码" />
</el-form-item> </a-form-item>
<el-form-item> <a-form-item name="code">
<el-input v-model="loginForm.code" placeholder="验证码" /> <a-input v-model:value="loginForm.code" placeholder="验证码">
<img :src="captchaImage" @click="refreshCaptcha" style="cursor: pointer" /> <template #suffix>
</el-form-item> <img :src="captchaImage" @click="refreshCaptcha" style="cursor: pointer" />
<el-button type="primary" @click="handleLogin">登录</el-button> </template>
</el-form> </a-input>
</a-form-item>
<a-button type="primary" block @click="handleLogin">登录</a-button>
</a-form>
</template> </template>
<script setup> <script setup>
@@ -70,6 +73,8 @@ onMounted(() => refreshCaptcha());
## 完整登录示例(含失败处理) ## 完整登录示例(含失败处理)
```typescript ```typescript
import { message } from 'ant-design-vue';
const loading = ref(false); const loading = ref(false);
const handleLogin = async () => { const handleLogin = async () => {
@@ -78,11 +83,11 @@ const handleLogin = async () => {
try { try {
await userStore.login(loginForm.value); await userStore.login(loginForm.value);
ElMessage.success('登录成功'); message.success('登录成功');
router.push('/'); router.push('/');
} catch (e: any) { } catch (e: any) {
// 登录失败(验证码错误/密码错误等),刷新验证码 // 登录失败(验证码错误/密码错误等),刷新验证码
ElMessage.error(e.message || '登录失败'); message.error(e.message || '登录失败');
refreshCaptcha(); refreshCaptcha();
loginForm.value.code = ''; loginForm.value.code = '';
} finally { } finally {
@@ -97,77 +97,83 @@ export function exampleBatchDelete(data: any) {
```typescript ```typescript
// ui/src/views/tool/example/columns.ts // ui/src/views/tool/example/columns.ts
import { h } from 'vue'; import { h } from 'vue';
import { ElAvatar, ElTag } from 'element-plus'; import { Tag, Avatar } from 'ant-design-vue';
export const columns = [ export const columns = [
{ {
type: 'selection', // 多选框列 title: 'ID',
}, dataIndex: 'id',
{
label: 'ID',
prop: 'id',
fixed: 'left', fixed: 'left',
width: 50, width: 50,
}, },
{ {
label: '名称', title: '名称',
prop: 'name', dataIndex: 'name',
minWidth: 120, minWidth: 120,
}, },
{ {
label: '状态', title: '状态',
prop: 'statusText', dataIndex: 'statusText',
minWidth: 100, minWidth: 100,
// 选项标签渲染:按值映射文字与颜色 // 选项标签渲染:按值映射文字与颜色
render(record) { customRender({ record }) {
const statusMap = { const statusMap = {
2: { text: '停用', type: 'danger' }, 2: { text: '停用', color: 'error' },
1: { text: '在用', type: 'success' }, 1: { text: '在用', color: 'success' },
}; };
const status = statusMap[record.row.status]; const status = statusMap[record.status];
return h(ElTag, { type: status?.type || 'info' }, { return h(Tag, { color: status?.color || 'default' }, {
default: () => status?.text || record.row.statusText, default: () => status?.text || record.statusText,
}); });
}, },
}, },
{ {
label: '头像', title: '头像',
prop: 'avatar', dataIndex: 'avatar',
minWidth: 80, minWidth: 80,
// 图片渲染:以方形头像形式展示 // 图片渲染:以方形头像形式展示
render(record) { customRender({ record }) {
return h(ElAvatar, { return h(Avatar, {
size: 48, size: 48,
src: record.row.avatar, src: record.avatar,
shape: 'square', shape: 'square',
fit: 'fill',
}); });
}, },
}, },
{ {
label: '创建人', title: '创建人',
prop: 'createUser', dataIndex: 'createUser',
minWidth: 100, minWidth: 100,
}, },
{ {
label: '创建时间', title: '创建时间',
prop: 'createTime', dataIndex: 'createTime',
width: 180,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 180, width: 180,
}, },
]; ];
``` ```
:::tip 多选列
AntDesign 表格的多选框列不在 `columns` 中定义,而是通过表格的 `:row-selection` 属性开启(见步骤四)。
:::
### 列配置字段说明 ### 列配置字段说明
| 字段 | 类型 | 说明 | | 字段 | 类型 | 说明 |
|------|------|------| |------|------|------|
| `type` | string | 列类型:`selection`(多选框)/ `index`(序号) | | `title` | string | 表头标题 |
| `label` | string | 表头标题 | | `dataIndex` | string | 数据字段名(camelCase,与后端响应一致) |
| `prop` | string | 数据字段名(camelCase,与后端响应一致) | | `key` | string | 列标识(`#bodyCell` 插槽按 `column.key` 匹配) |
| `fixed` | string | 固定列:`left` / `right` | | `fixed` | string | 固定列:`left` / `right` |
| `width` | number | 固定宽度 | | `width` | number | 固定宽度 |
| `minWidth` | number | 最小宽度(自适应) | | `minWidth` | number | 最小宽度(自适应) |
| `render` | function | 自定义渲染函数 | | `customRender` | function | 自定义渲染函数(参数 `{ record }`) |
:::tip 状态值对齐 :::tip 状态值对齐
`statusMap` 的值域必须与数据库/字典一致(如 example:1=在用 2=停用)。推荐直接使用后端补全的 `statusText` 字段,仅颜色按值映射。 `statusMap` 的值域必须与数据库/字典一致(如 example:1=在用 2=停用)。推荐直接使用后端补全的 `statusText` 字段,仅颜色按值映射。
@@ -181,21 +187,21 @@ import { FormSchema } from '@/components/Form/index';
export const schemas: FormSchema[] = [ export const schemas: FormSchema[] = [
{ {
field: 'name', name: 'name',
component: 'Input', component: 'Input',
label: '名称', label: '名称',
componentProps: { componentProps: {
placeholder: '请输入名称', placeholder: '请输入名称',
clearable: true, allowClear: true,
}, },
}, },
{ {
field: 'status', name: 'status',
component: 'Select', component: 'Select',
label: '状态', label: '状态',
componentProps: { componentProps: {
placeholder: '请选择状态', placeholder: '请选择状态',
clearable: true, allowClear: true,
options: [ options: [
{ label: '在用', value: 1 }, { label: '在用', value: 1 },
{ label: '停用', value: 2 }, { label: '停用', value: 2 },
@@ -215,7 +221,7 @@ export const schemas: FormSchema[] = [
| `DatePicker` | 日期字段 | 日期选择器 | | `DatePicker` | 日期字段 | 日期选择器 |
:::tip 查询字段须与后端配置对应 :::tip 查询字段须与后端配置对应
`field` 必须是后端 Service `pageLikeFields`(模糊)或 `pageEqFields`(精确)中声明的字段,否则查询条件不生效(见 [8.3 分页查询规范](/zh/api-spec/pagination.html))。下拉选项也可用 `getDictItemByCode(code)` 从字典动态加载。 `name` 必须是后端 Service `pageLikeFields`(模糊)或 `pageEqFields`(精确)中声明的字段,否则查询条件不生效(见 [8.3 分页查询规范](/zh/api-spec/pagination.html))。下拉选项也可用 `getDictItemByCode(code)` 从字典动态加载。
::: :::
## 步骤四:创建列表页 ## 步骤四:创建列表页
@@ -228,42 +234,42 @@ export const schemas: FormSchema[] = [
<SearchForm :schemas="querySchemas" @search="handleSearch" @reset="handleReset" /> <SearchForm :schemas="querySchemas" @search="handleSearch" @reset="handleReset" />
<!-- 操作栏 --> <!-- 操作栏 -->
<el-row :gutter="10" style="margin-bottom: 10px;"> <a-space style="margin-bottom: 10px;">
<el-col :span="1.5"> <a-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</a-button>
<el-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</el-button> <a-button v-perm="['sys:example:batchDelete']" danger :disabled="!selectedIds.length" @click="handleBatchDelete">
</el-col> 批量删除
<el-col :span="1.5"> </a-button>
<el-button v-perm="['sys:example:batchDelete']" type="danger" :disabled="!selectedIds.length" @click="handleBatchDelete"> </a-space>
批量删除
</el-button>
</el-col>
</el-row>
<!-- 数据表格 --> <!-- 数据表格 -->
<el-table :data="tableData" @selection-change="handleSelectionChange" v-loading="loading"> <a-spin :spinning="loading">
<el-table-column v-for="col in columns" v-bind="col" :key="col.prop || col.type"> <a-table
<template v-if="col.render" #default="scope"> :data-source="tableData"
<component :is="() => col.render(scope)" /> :columns="columns"
:row-selection="{ selectedRowKeys: selectedIds, onChange: handleSelectionChange }"
:pagination="false"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-button v-perm="['sys:example:update']" type="link" @click="handleEdit(record)">编辑</a-button>
<a-button v-perm="['sys:example:detail']" type="link" @click="handleDetail(record)">详情</a-button>
<a-button v-perm="['sys:example:delete']" type="link" danger @click="handleDelete(record.id)">删除</a-button>
</template>
</template> </template>
</el-table-column> </a-table>
<el-table-column label="操作" width="180" fixed="right"> </a-spin>
<template #default="{ row }">
<el-button v-perm="['sys:example:update']" type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button v-perm="['sys:example:detail']" type="primary" link @click="handleDetail(row)">详情</el-button>
<el-button v-perm="['sys:example:delete']" type="danger" link @click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 --> <!-- 分页 -->
<el-pagination <a-pagination
v-model:current-page="pageNo" v-model:current="pageNo"
v-model:page-size="pageSize" v-model:page-size="pageSize"
:total="total" :total="total"
:page-sizes="[10, 20, 50, 100]" :show-size-changer="true"
layout="total, sizes, prev, pager, next, jumper" :page-size-options="[10, 20, 50, 100]"
:show-total="(t) => `共 ${t} 条`"
style="margin-top: 10px; text-align: right;"
@change="fetchData" @change="fetchData"
style="margin-top: 10px; justify-content: flex-end;"
/> />
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
@@ -278,7 +284,7 @@ export const schemas: FormSchema[] = [
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example'; import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
import { columns } from './columns'; import { columns } from './columns';
import { schemas as querySchemas } from './querySchemas'; import { schemas as querySchemas } from './querySchemas';
import { ElMessage, ElMessageBox } from 'element-plus'; import { message, Modal } from 'ant-design-vue';
import EditDialog from './edit.vue'; import EditDialog from './edit.vue';
import DetailDialog from './detail.vue'; import DetailDialog from './detail.vue';
@@ -336,24 +342,34 @@ const handleDetail = (row) => {
}; };
// 删除 // 删除
const handleDelete = async (id) => { const handleDelete = (id) => {
await ElMessageBox.confirm('确认删除该记录?', '提示', { type: 'warning' }); Modal.confirm({
await exampleDelete(id); title: '提示',
ElMessage.success('删除成功'); content: '确认删除该记录?',
fetchData(); onOk: async () => {
await exampleDelete(id);
message.success('删除成功');
fetchData();
},
});
}; };
// 批量删除(DELETE,请求体 { ids: [...] }) // 批量删除(DELETE,请求体 { ids: [...] })
const handleBatchDelete = async () => { const handleBatchDelete = () => {
await ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条记录?`, '提示', { type: 'warning' }); Modal.confirm({
await exampleBatchDelete({ ids: selectedIds.value }); title: '提示',
ElMessage.success('删除成功'); content: `确认删除选中的 ${selectedIds.value.length} 条记录?`,
fetchData(); onOk: async () => {
await exampleBatchDelete({ ids: selectedIds.value });
message.success('删除成功');
fetchData();
},
});
}; };
// 多选变化 // 多选变化(row-selection 的 onChange 返回选中行的 row-key 数组)
const handleSelectionChange = (rows) => { const handleSelectionChange = (keys) => {
selectedIds.value = rows.map(row => row.id); selectedIds.value = keys;
}; };
onMounted(() => fetchData()); onMounted(() => fetchData());
@@ -365,37 +381,39 @@ onMounted(() => fetchData());
```vue ```vue
<!-- ui/src/views/tool/example/edit.vue --> <!-- ui/src/views/tool/example/edit.vue -->
<template> <template>
<el-dialog v-model="visible" :title="formData.id ? '编辑' : '新增'" width="500px"> <a-modal v-model:visible="visible" :title="formData.id ? '编辑' : '新增'" width="500px">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> <a-form ref="formRef" :model="formData" :rules="rules" :label-col="{ style: { width: '80px' } }">
<el-form-item label="名称" prop="name"> <a-form-item label="名称" name="name">
<el-input v-model="formData.name" placeholder="请输入名称" /> <a-input v-model:value="formData.name" placeholder="请输入名称" allow-clear />
</el-form-item> </a-form-item>
<el-form-item label="类型" prop="type"> <a-form-item label="类型" name="type">
<el-select v-model="formData.type" placeholder="请选择类型"> <a-select v-model:value="formData.type" placeholder="请选择类型" style="width: 100%">
<el-option v-for="item in typeOptions" :key="item.value" :label="item.name" :value="item.value" /> <a-select-option v-for="item in typeOptions" :key="item.value" :value="item.value">
</el-select> {{ item.name }}
</el-form-item> </a-select-option>
<el-form-item label="状态" prop="status"> </a-select>
<el-radio-group v-model="formData.status"> </a-form-item>
<el-radio :value="1">在用</el-radio> <a-form-item label="状态" name="status">
<el-radio :value="2">停用</el-radio> <a-radio-group v-model:value="formData.status">
</el-radio-group> <a-radio :value="1">在用</a-radio>
</el-form-item> <a-radio :value="2">停用</a-radio>
<el-form-item label="排序" prop="sort"> </a-radio-group>
<el-input-number v-model="formData.sort" :min="0" :max="9999" /> </a-form-item>
</el-form-item> <a-form-item label="排序" name="sort">
</el-form> <a-input-number v-model:value="formData.sort" :min="0" :max="9999" />
</a-form-item>
</a-form>
<template #footer> <template #footer>
<el-button @click="visible = false">取消</el-button> <a-button @click="visible = false">取消</a-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button> <a-button type="primary" :loading="submitting" @click="handleSubmit">确定</a-button>
</template> </template>
</el-dialog> </a-modal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { exampleAdd, exampleUpdate, getExampleDetail } from '@/api/tool/example'; import { exampleAdd, exampleUpdate, getExampleDetail } from '@/api/tool/example';
import { getDictItemByCode } from '@/api/common/index'; import { getDictItemByCode } from '@/api/common/index';
import { ElMessage } from 'element-plus'; import { message } from 'ant-design-vue';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
@@ -413,8 +431,8 @@ const formData = ref({
}); });
const rules = { const rules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }], name: { required: true, message: '请输入名称', trigger: 'blur' },
type: [{ required: true, message: '请选择类型', trigger: 'change' }], type: { required: true, message: '请选择类型', trigger: 'change' },
}; };
// 打开弹窗 // 打开弹窗
@@ -439,10 +457,10 @@ const handleSubmit = async () => {
try { try {
if (formData.value.id) { if (formData.value.id) {
await exampleUpdate(formData.value); await exampleUpdate(formData.value);
ElMessage.success('修改成功'); message.success('修改成功');
} else { } else {
await exampleAdd(formData.value); await exampleAdd(formData.value);
ElMessage.success('新增成功'); message.success('新增成功');
} }
visible.value = false; visible.value = false;
emit('success'); emit('success');
@@ -464,43 +482,43 @@ defineExpose({ open });
```vue ```vue
<!-- ui/src/views/tool/example/detail.vue --> <!-- ui/src/views/tool/example/detail.vue -->
<template> <template>
<el-dialog v-model="props.visible" title="案例详情" width="750" :close-on-click-modal="false"> <a-modal v-model:visible="props.visible" title="案例详情" width="750px" :mask-closable="false">
<el-descriptions column="2" border> <a-descriptions :column="2" bordered>
<el-descriptions-item label="名称:"> <a-descriptions-item label="名称">
{{ formData.name || '-' }} {{ formData.name || '-' }}
</el-descriptions-item> </a-descriptions-item>
<el-descriptions-item label="类型:"> <a-descriptions-item label="类型">
{{ formData.typeText || '-' }} {{ formData.typeText || '-' }}
</el-descriptions-item> </a-descriptions-item>
<el-descriptions-item label="状态:"> <a-descriptions-item label="状态">
<el-tag :type="formData.status === 1 ? 'success' : 'danger'"> <a-tag :color="formData.status === 1 ? 'success' : 'error'">
{{ formData.statusText || '-' }} {{ formData.statusText || '-' }}
</el-tag> </a-tag>
</el-descriptions-item> </a-descriptions-item>
<el-descriptions-item label="排序:"> <a-descriptions-item label="排序">
{{ formData.sort ?? '-' }} {{ formData.sort ?? '-' }}
</el-descriptions-item> </a-descriptions-item>
<el-descriptions-item label="头像:"> <a-descriptions-item label="头像">
<el-image <a-image
v-if="formData.avatar" v-if="formData.avatar"
:src="formData.avatar" :src="formData.avatar"
:preview-src-list="[formData.avatar]" :width="80"
fit="cover" :height="80"
style="width: 80px; height: 80px" style="object-fit: cover"
/> />
<span v-else>-</span> <span v-else>-</span>
</el-descriptions-item> </a-descriptions-item>
<el-descriptions-item label="创建人:"> <a-descriptions-item label="创建人">
{{ formData.createUser || '-' }} {{ formData.createUser || '-' }}
</el-descriptions-item> </a-descriptions-item>
<el-descriptions-item label="创建时间:"> <a-descriptions-item label="创建时间">
{{ formData.createTime || '-' }} {{ formData.createTime || '-' }}
</el-descriptions-item> </a-descriptions-item>
</el-descriptions> </a-descriptions>
<template #footer> <template #footer>
<el-button @click="emit('update:visible', false)">关闭</el-button> <a-button @click="emit('update:visible', false)">关闭</a-button>
</template> </template>
</el-dialog> </a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -1,7 +1,7 @@
# 9.1 前端工程概述 # 9.1 前端工程概述
:::tip 概述 :::tip 概述
前端基于 Vue 3 + TypeScript + Vite + ElementPlus 构建,采用 Pinia 状态管理,支持动态菜单和权限控制。通过 Axios 封装统一处理请求/响应/错误,通过 `v-perm` 指令实现按钮级权限控制。前端为本仓库 **`ui/` 目录**内的 EleVue 管理后台,与本后端通过 API 对接。 前端基于 Vue 3 + TypeScript + Vite + AntDesign 构建,采用 Pinia 状态管理,支持动态菜单和权限控制。通过 Axios 封装统一处理请求/响应/错误,通过 `v-perm` 指令实现按钮级权限控制。前端为本仓库 **`ui/` 目录**内的 AntdVue 管理后台,与本后端通过 API 对接。
::: :::
## 技术栈 ## 技术栈
@@ -10,7 +10,7 @@
|------|------|------|------| |------|------|------|------|
| 框架 | Vue | 3.4+ | 核心 UI 框架 | | 框架 | Vue | 3.4+ | 核心 UI 框架 |
| 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 | | 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 |
| UI 库 | ElementPlus | 2.7+ | 组件库 | | UI 库 | AntDesign Vue | 3.2+(实际 3.2.20) | 组件库 |
| 构建 | Vite | 3.2+(实际 ^3.2.10) | 开发服务器与构建 | | 构建 | Vite | 3.2+(实际 ^3.2.10) | 开发服务器与构建 |
| 状态 | Pinia | 2.1+ | 全局状态管理 | | 状态 | Pinia | 2.1+ | 全局状态管理 |
| 路由 | Vue Router | 4.3+ | 路由管理 | | 路由 | Vue Router | 4.3+ | 路由管理 |
@@ -36,7 +36,7 @@
`v-perm` / `v-perms` 指令控制按钮级权限: `v-perm` / `v-perms` 指令控制按钮级权限:
```vue ```vue
<el-button v-perm="['sys:user:add']">新增</el-button> <a-button v-perm="['sys:user:add']">新增</a-button>
``` ```
### Token 管理 ### Token 管理
@@ -96,7 +96,7 @@ ui/src/
```bash ```bash
pnpm install # 安装依赖(或 npm install) pnpm install # 安装依赖(或 npm install)
pnpm dev # 启动开发服务器(默认 8001) pnpm dev # 启动开发服务器(默认 8002)
pnpm build # 构建生产版本 pnpm build # 构建生产版本
pnpm preview # 预览构建结果 pnpm preview # 预览构建结果
pnpm lint:eslint # ESLint 检查 pnpm lint:eslint # ESLint 检查
@@ -11,7 +11,7 @@
```vue ```vue
<template> <template>
<!-- 只有拥有 sys:user:add 权限的用户才能看到此按钮 --> <!-- 只有拥有 sys:user:add 权限的用户才能看到此按钮 -->
<el-button v-perm="['sys:user:add']" type="primary">新增</el-button> <a-button v-perm="['sys:user:add']" type="primary">新增</a-button>
</template> </template>
``` ```
@@ -20,7 +20,7 @@
```vue ```vue
<template> <template>
<!-- 拥有 sys:user:add 或 sys:user:update 任一权限即可 --> <!-- 拥有 sys:user:add 或 sys:user:update 任一权限即可 -->
<el-button v-perms="['sys:user:add', 'sys:user:update']">操作</el-button> <a-button v-perms="['sys:user:add', 'sys:user:update']">操作</a-button>
</template> </template>
``` ```
@@ -10,9 +10,7 @@
|-------|------|------| |-------|------|------|
| userStore | `store/modules/user.ts` | Token / 用户信息 / 权限列表 / 登录登出 | | userStore | `store/modules/user.ts` | Token / 用户信息 / 权限列表 / 登录登出 |
| asyncRouteStore | `store/modules/asyncRoute.ts` | 动态路由 / keep-alive 缓存 | | asyncRouteStore | `store/modules/asyncRoute.ts` | 动态路由 / keep-alive 缓存 |
| projectSettingStore | `store/modules/projectSetting.ts` | 项目配置 | | projectSettingStore | `store/modules/projectSetting.ts` | 项目配置(导航/顶栏主题等) |
| ossConfigStore | `store/modules/ossConfig.ts` | 云存储配置 |
| designSettingStore | `store/modules/designSetting.ts` | 主题设计 |
| lockscreenStore | `store/modules/lockscreen.ts` | 锁屏 | | lockscreenStore | `store/modules/lockscreen.ts` | 锁屏 |
| tabsViewStore | `store/modules/tabsView.ts` | 标签页 | | tabsViewStore | `store/modules/tabsView.ts` | 标签页 |
+101 -65
View File
@@ -1,115 +1,151 @@
# 9.14 样式与主题定制 # 9.14 样式与主题定制
:::tip 概述 :::tip 概述
前端使用 TailwindCSS + SCSS 实现样式管理,支持主题色切换和暗色模式。 前端使用 WindiCSS + Less 实现样式管理,通过主题预处理器支持默认/暗色双主题切换。
::: :::
## 技术栈 ## 技术栈
| 技术 | 用途 | | 技术 | 用途 |
|------|------| |------|------|
| TailwindCSS | 原子化 CSS 框架 | | WindiCSS | 原子化 CSS 框架(Tailwind 语法兼容) |
| SCSS | 全局样式变量和主题 | | Less | 全局样式变量和主题 |
| ElementPlus 主题变量 | 组件样式覆盖 | | AntDesign Vue 主题变量 | 组件样式覆盖(antd.less 变量) |
## 全局样式文件 ## 全局样式文件
```text ```text
ui/src/styles/ ui/src/styles/
├── index.scss # 全局样式入口(@import 所有子模块) ├── index.less # 全局样式入口(@import 过渡动画与公共样式)
├── var.scss # SCSS 变量定义(颜色、尺寸等) ├── color.less # 颜色变量定义(内容区背景、基础色)
├── common.scss # 公共样式类 ├── common.less # 公共样式类(页面容器、滚动条、字体)
├── tailwind.css # TailwindCSS 入口 ├── config.less # 配置样式(@import 引用 color.less)
├── tailwindbase.css # TailwindCSS 基础重置 ├── tailwind.css # Tailwind/Windi 兼容入口(可选引入)
├── theme/ ├── theme/
│ ├── light.scss # 浅色主题(含 ElementPlus 组件覆盖) │ ├── theme-default.less # 默认主题(引入 antd 默认主题变量)
│ └── dark.scss # 深色主题 │ └── theme-dark.less # 暗色主题(引入 antd 暗色主题变量)
└── transition/ └── transition/ # 过渡动画(index/base/fade/scale/scroll/slide/zoom)
└── index.scss # 过渡动画
``` ```
## SCSS 变量 ## 主题文件
```scss 主题文件直接引入 ant-design-vue 官方主题,通过 `@zougt/vite-plugin-theme-preprocessor` 编译为多套作用域变量:
/* ui/src/styles/var.scss */
$primaryColor: #165DFF; ```less
$primaryColorHover: #57a3f3; /* ui/src/styles/theme/theme-default.less */
$header-height: 64px; @import "ant-design-vue/lib/style/themes/default.less";
$footer-height: 70px;
``` ```
## 主题色切换 ```less
/* ui/src/styles/theme/theme-dark.less */
@import "ant-design-vue/lib/style/themes/dark.less";
```
通过 `designSettingStore` 管理主题: 构建时的主题作用域配置(`build/vite/plugin/theme.ts`):
```typescript ```typescript
import { useDesignSettingStore } from '@/store/modules/designSetting'; themePreprocessorPlugin({
less: {
const designStore = useDesignSettingStore(); multipleScopeVars: [
{ scopeName: 'theme-default', path: path.resolve('src/styles/theme/theme-default.less') },
// 切换暗色模式 { scopeName: 'theme-dark', path: path.resolve('src/styles/theme/theme-dark.less') },
designStore.setDarkMode(true); ],
extract: false, // 不抽取独立主题 CSS 文件
// 获取当前主题色 },
const primaryColor = designStore.appTheme; });
``` ```
## ElementPlus 主题覆盖 :::tip 双主题切换
页面挂载不同的作用域类名(`theme-default` / `theme-dark`)即可整体切换深浅色,所有 antd 组件变量随作用域自动切换。
:::
ElementPlus 组件样式覆盖定义在主题文件中,通过 `[data-theme]` 选择器区分深浅色模式: ## 颜色变量示例
```scss ```less
/* ui/src/styles/theme/light.scss */ /* ui/src/styles/color.less */
[data-theme='light'] { html {
--el-border-color-base: #e4e7ed; //content
--content-bg: #f0f2f5;
}
.el-table { @white: #fff;
--el-table-header-bg-color: #f5f7fa; @content-bg: #f0f2f5;
--el-table-header-text-color: rgb(31 34 37); ```
}
// ...更多组件覆盖 ## 主题配置管理
布局主题(导航/顶栏配色)通过 `projectSetting` Store 管理:
```typescript
import { useProjectSettingStore } from '@/store/modules/projectSetting';
const projectStore = useProjectSettingStore();
// 导航主题 / 顶栏主题
projectStore.navTheme;
projectStore.headerTheme;
projectStore.setNavTheme('dark');
```
## AntDesign 组件覆盖
如需覆盖 antd 组件样式,写在作用域主题文件或 `common.less` 中,使用 `.ant-*` 选择器:
```less
/* ui/src/styles/theme/theme-default.less 或页面 scoped 样式 */
.ant-table-thead > tr > th {
background: #f5f7fa;
} }
``` ```
## TailwindCSS 配置 :::warning 覆盖要点
- antd 样式类名为 `.ant-*`(如 `.ant-btn`、`.ant-table`),与 ElementPlus 的 `.el-*` 不同;
- 优先调整主题变量(`@primary-color` 等),其次才是选择器覆盖;
- 暗色主题的覆盖要放进 `theme-dark.less` 作用域,避免深浅色互相污染。
:::
```javascript ## WindiCSS 配置
// ui/tailwind.config.js
module.exports = { ```typescript
content: ['./src/**/*.{vue,ts,tsx}'], // ui/windi.config.ts
import { defineConfig } from 'vite-plugin-windicss';
export default defineConfig({
darkMode: 'class',
theme: { theme: {
extend: { extend: {
colors: { screens: {
primary: 'var(--primary-color)', sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
'2xl': '1600px',
}, },
}, },
}, },
}; });
``` ```
## 自定义全局样式 ## 自定义全局样式
```scss ```less
/* ui/src/styles/index.scss */ /* ui/src/styles/common.less */
// 页面容器 // 页面容器
.page-container { #app {
padding: 16px; width: 100%;
background: var(--bg-color); height: 100%;
border-radius: 8px;
} }
// 搜索表单 // 滚动条
.search-form { *::-webkit-scrollbar {
margin-bottom: 16px; width: 12px;
padding: 16px; height: 12px;
background: #fafafa;
border-radius: 4px;
} }
``` ```
:::tip 样式约定 :::tip 样式约定
- 优先使用 TailwindCSS 原子类(布局/间距/颜色) - 优先使用 WindiCSS 原子类(布局/间距/颜色)
- 组件级覆盖写在 `theme/*.scss`,避免散落在页面中 - 组件级覆盖写进 `theme/*.less` 作用域,避免散落在页面中
- 与后端联调时注意:列表状态 Tag 颜色应与字典值语义一致(如 1=在用 success / 2=停用 danger) - 与后端联调时注意:列表状态 Tag 颜色应与字典值语义一致(如 1=在用 success / 2=停用 error)
::: :::
@@ -23,7 +23,7 @@
| `ui/src/views/{app}/{name}/index.vue` | index.vue.tpl | 列表页(含搜索、表格、新增/编辑/详情弹窗、导入导出按钮) | | `ui/src/views/{app}/{name}/index.vue` | index.vue.tpl | 列表页(含搜索、表格、新增/编辑/详情弹窗、导入导出按钮) |
| `ui/src/views/{app}/{name}/edit.vue` | edit.vue.tpl | 编辑弹窗(新增/复用) | | `ui/src/views/{app}/{name}/edit.vue` | edit.vue.tpl | 编辑弹窗(新增/复用) |
| `ui/src/views/{app}/{name}/detail.vue` | detail.vue.tpl | 详情弹窗 | | `ui/src/views/{app}/{name}/detail.vue` | detail.vue.tpl | 详情弹窗 |
| `ui/src/views/{app}/{name}/columns.ts` | columns.ts.tpl | 表格列配置(含图片 ElAvatar、选项 ElTag、链接等渲染) | | `ui/src/views/{app}/{name}/columns.ts` | columns.ts.tpl | 表格列配置(含图片 AAvatar、选项 ATag、链接等渲染) |
| `ui/src/views/{app}/{name}/querySchemas.ts` | querySchemas.ts.tpl | 查询表单配置(按字段类型自动选 Input/Select/InputNumber/DatePicker) | | `ui/src/views/{app}/{name}/querySchemas.ts` | querySchemas.ts.tpl | 查询表单配置(按字段类型自动选 Input/Select/InputNumber/DatePicker) |
### 前端文件(ui2/ 树形模板 — 4 个文件) ### 前端文件(ui2/ 树形模板 — 4 个文件)
@@ -65,7 +65,7 @@ public function getTableList(): array
| 类型 | VARCHAR | | 类型 | VARCHAR |
| 匹配 | 注释或字段名匹配 `/图片\|封面\|头像\|logo\|icon\|image\|avatar\|photo/i` | | 匹配 | 注释或字段名匹配 `/图片\|封面\|头像\|logo\|icon\|image\|avatar\|photo/i` |
识别后:`is_image = true`,归入 `file` 字段组,前端用 ElAvatar 渲染。 识别后:`is_image = true`,归入 `file` 字段组,前端用 AAvatar(`Avatar`)渲染。
### 富文本字段 ### 富文本字段
@@ -103,12 +103,14 @@ public function getTableList(): array
标签颜色映射(`guessChoiceTagType()`): 标签颜色映射(`guessChoiceTagType()`):
| 选项值 | 标签颜色 | Element Plus type | | 选项值 | 标签颜色 | type(`guessChoiceTagType()`) | AntDesign `color`(模板映射) |
|--------|---------|-------------------| |--------|---------|-------------------|-------------------|
| 1 | 绿色 | `success` | | 1 | 绿色 | `success` | `success` |
| 2 | 红色 | `danger` | | 2 | 红色 | `danger` | `error` |
| 3 | 橙色 | `warning` | | 3 | 橙色 | `warning` | `warning` |
| 其他 | 灰色 | `info` | | 其他 | 灰色 | `info` | `default` |
(后端生成 `type`,`columns.ts.tpl` 渲染时映射为 ant-design-vue `Tag` 的 `color`:`danger`→`error`、`info`→`default`。)
识别后:`choices` 数组非空,自动归入 `eq` 字段组(精确匹配)和 `serialize_maps`(字典映射,编码为 `{模块名}_{字段名}`)。 识别后:`choices` 数组非空,自动归入 `eq` 字段组(精确匹配)和 `serialize_maps`(字典映射,编码为 `{模块名}_{字段名}`)。
@@ -125,7 +125,7 @@ $field = [
'in_list' => true, // 是否进列表 'in_list' => true, // 是否进列表
'filterable' => false, // 是否可筛选 'filterable' => false, // 是否可筛选
'filter_type' => '', // 筛选方式:'精确匹配' / '' / '模糊' 'filter_type' => '', // 筛选方式:'精确匹配' / '' / '模糊'
'choices' => [ // 选项列表(有值时可生成 ElTag 渲染) 'choices' => [ // 选项列表(有值时可生成 ATag 渲染)
['value' => 0, 'label' => '正常', 'type' => 'success'], ['value' => 0, 'label' => '正常', 'type' => 'success'],
['value' => 1, 'label' => '禁用', 'type' => 'danger'], ['value' => 1, 'label' => '禁用', 'type' => 'danger'],
], ],
@@ -177,28 +177,28 @@ class <?php echo $model_class_name ?>Service extends <?php echo $is_tree ? 'Base
```typescript ```typescript
import { h } from 'vue'; import { h } from 'vue';
import { ElAvatar, ElTag } from 'element-plus'; import { Tag, Avatar } from 'ant-design-vue';
export const columns = [ export const columns = [
{ type: 'selection' }, { title: 'ID', dataIndex: 'id', fixed: 'left', width: 50 },
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
<?php foreach ($list_fields as $field): ?> <?php foreach ($list_fields as $field): ?>
{ {
label: '<?php echo $field['label'] ?>', title: '<?php echo $field['label'] ?>',
prop: '<?php echo $field['camel_name'] ?>', dataIndex: '<?php echo $field['camel_name'] ?>',
key: '<?php echo $field['camel_name'] ?>',
<?php if ($field['is_image']): ?> <?php if ($field['is_image']): ?>
render(record) { customRender({ record }) {
return h(ElAvatar, { size: 48, src: record.row.<?php echo $field['camel_name'] ?> }); return h(Avatar, { size: 48, src: record.<?php echo $field['camel_name'] ?>, shape: 'square' });
}, },
<?php elseif ($field['choices']): ?> <?php elseif ($field['choices']): ?>
render(record) { customRender({ record }) {
const map = { const map = {
<?php foreach ($field['choices'] as $choice): ?> <?php foreach ($field['choices'] as $choice): ?>
<?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', type: '<?php echo $choice['type'] ?>' }, <?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', color: '<?php echo ($choice['type'] === 'danger') ? 'error' : (($choice['type'] === 'info') ? 'default' : $choice['type']) ?>' },
<?php endforeach; ?> <?php endforeach; ?>
}; };
const s = map[record.row.<?php echo $field['camel_name'] ?>]; const s = map[record.<?php echo $field['camel_name'] ?>];
return h(ElTag, { type: s?.type || 'info' }, { default: () => s?.text }); return h(Tag, { color: s?.color || 'default' }, { default: () => s?.text });
}, },
<?php endif; ?> <?php endif; ?>
}, },
@@ -240,7 +240,7 @@ export const columns = [
| `index.vue` | 列表页 | 导入 columns.ts、querySchemas.ts | | `index.vue` | 列表页 | 导入 columns.ts、querySchemas.ts |
| `edit.vue` | 编辑弹窗 | 被 index.vue 动态加载 | | `edit.vue` | 编辑弹窗 | 被 index.vue 动态加载 |
| `detail.vue` | 详情弹窗 | 被 index.vue 动态加载 | | `detail.vue` | 详情弹窗 | 被 index.vue 动态加载 |
| `columns.ts` | 表格列配置 | 被 index.vue 导入,含 ElTag/ElAvatar 渲染 | | `columns.ts` | 表格列配置 | 被 index.vue 导入,含 ATag/AAvatar 渲染 |
| `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 Select/Input/DatePicker 组件 | | `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 Select/Input/DatePicker 组件 |
## 相关文档 ## 相关文档
@@ -154,7 +154,7 @@
# GET /api/generator/preview # GET /api/generator/preview
# Content-Type: application/json # Content-Type: application/json
# Authorization: Bearer <token> # Authorization: Bearer <token>
curl -X GET http://localhost:8000/api/generator/preview \ curl -X GET http://admin.laravel13.antdvue/api/generator/preview \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \ -H "Authorization: Bearer <token>" \
-d '{ -d '{
@@ -147,7 +147,7 @@ HTTP 封装(Token 携带、401 处理、统一错误提示、camel/snake 转
| `querySchemas.ts` | 搜索表单字段定义 | | `querySchemas.ts` | 搜索表单字段定义 |
:::tip 关于以下示例 :::tip 关于以下示例
以下代码使用原生 `el-table` / `el-form` 简化展示核心逻辑。实际项目中应使用封装好的 `BasicTable` + `useForm` 等组合式组件,详见 [第 9 章组件文档](/zh/frontend/overview.html)。 以下代码使用原生 `a-table` / `a-form` 简化展示核心逻辑。实际项目中应使用封装好的 `BasicTable` + `useForm` 等组合式组件,详见 [第 9 章组件文档](/zh/frontend/overview.html)。
::: :::
`index.vue`(列表页)示例: `index.vue`(列表页)示例:
@@ -157,50 +157,56 @@ HTTP 封装(Token 携带、401 处理、统一错误提示、camel/snake 转
<template> <template>
<div> <div>
<!-- 搜索表单 --> <!-- 搜索表单 -->
<el-form :model="searchForm" inline> <a-form :model="searchForm" layout="inline">
<el-form-item label="名称"> <a-form-item label="名称">
<el-input v-model="searchForm.name" placeholder="请输入名称" /> <a-input v-model:value="searchForm.name" placeholder="请输入名称" allow-clear />
</el-form-item> </a-form-item>
<el-form-item label="状态"> <a-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择" clearable> <a-select v-model:value="searchForm.status" placeholder="请选择" allow-clear style="width: 160px">
<el-option label="启用" :value="1" /> <a-select-option :value="1">启用</a-select-option>
<el-option label="停用" :value="2" /> <a-select-option :value="2">停用</a-select-option>
</el-select> </a-select>
</el-form-item> </a-form-item>
<el-form-item> <a-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button> <a-button type="primary" @click="handleSearch">搜索</a-button>
<el-button @click="handleReset">重置</el-button> <a-button @click="handleReset">重置</a-button>
</el-form-item> </a-form-item>
</el-form> </a-form>
<!-- 操作按钮(v-perm 控制显示,与后端权限码一致) --> <!-- 操作按钮(v-perm 控制显示,与后端权限码一致) -->
<el-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</el-button> <a-space style="margin: 12px 0">
<el-button v-perm="['sys:example:batchDelete']" type="danger" <a-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</a-button>
:disabled="!selectedIds.length" @click="handleBatchDelete">批量删除</el-button> <a-button v-perm="['sys:example:batchDelete']" danger
:disabled="!selectedIds.length" @click="handleBatchDelete">批量删除</a-button>
</a-space>
<!-- 数据表格 --> <!-- 数据表格 -->
<el-table :data="tableData" @selection-change="handleSelectionChange"> <a-table
<el-table-column type="selection" width="55" /> :data-source="tableData"
<el-table-column prop="id" label="ID" width="80" /> :columns="columns"
<el-table-column prop="name" label="名称" /> :row-selection="{ selectedRowKeys: selectedIds, onChange: handleSelectionChange }"
<el-table-column prop="statusText" label="状态" width="100" /> :pagination="false"
<el-table-column prop="sort" label="排序" width="80" /> row-key="id"
<el-table-column prop="createTime" label="创建时间" width="180" /> >
<el-table-column label="操作" width="200"> <template #bodyCell="{ column, record }">
<template #default="{ row }"> <template v-if="column.key === 'action'">
<el-button v-perm="['sys:example:update']" size="small" @click="handleEdit(row)">编辑</el-button> <a-space>
<el-button v-perm="['sys:example:delete']" size="small" type="danger" <a-button v-perm="['sys:example:update']" size="small" @click="handleEdit(record)">编辑</a-button>
@click="handleDelete(row.id)">删除</el-button> <a-button v-perm="['sys:example:delete']" size="small" danger
@click="handleDelete(record.id)">删除</a-button>
</a-space>
</template> </template>
</el-table-column> </template>
</el-table> </a-table>
<!-- 分页(后端固定返回 records / total / current / size / pages) --> <!-- 分页(后端固定返回 records / total / current / size / pages) -->
<el-pagination <a-pagination
v-model:current-page="pageNo" v-model:current="pageNo"
v-model:page-size="pageSize" v-model:page-size="pageSize"
:total="total" :total="total"
@current-change="fetchData" :show-total="(t) => `共 ${t} 条`"
style="margin-top: 16px; text-align: right"
@change="fetchData"
/> />
</div> </div>
</template> </template>
@@ -209,6 +215,15 @@ HTTP 封装(Token 携带、401 处理、统一错误提示、camel/snake 转
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example'; import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
{ title: '名称', dataIndex: 'name', key: 'name' },
{ title: '状态', dataIndex: 'statusText', key: 'statusText', width: 100 },
{ title: '排序', dataIndex: 'sort', key: 'sort', width: 80 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
{ title: '操作', key: 'action', width: 200 },
];
const searchForm = ref({ name: '', status: '' }); const searchForm = ref({ name: '', status: '' });
const tableData = ref([]); const tableData = ref([]);
const pageNo = ref(1); const pageNo = ref(1);
@@ -230,7 +245,7 @@ const handleSearch = () => { pageNo.value = 1; fetchData(); };
const handleReset = () => { searchForm.value = { name: '', status: '' }; handleSearch(); }; const handleReset = () => { searchForm.value = { name: '', status: '' }; handleSearch(); };
const handleDelete = async (id: number) => { await exampleDelete(id); fetchData(); }; const handleDelete = async (id: number) => { await exampleDelete(id); fetchData(); };
const handleBatchDelete = async () => { await exampleBatchDelete({ ids: selectedIds.value }); fetchData(); }; const handleBatchDelete = async () => { await exampleBatchDelete({ ids: selectedIds.value }); fetchData(); };
const handleSelectionChange = (rows: any[]) => { selectedIds.value = rows.map((r) => r.id); }; const handleSelectionChange = (keys: any[]) => { selectedIds.value = keys; };
onMounted(() => fetchData()); onMounted(() => fetchData());
</script> </script>
@@ -249,7 +264,7 @@ tool/example/index
## 步骤四:测试联调 ## 步骤四:测试联调
1. 确保后端服务运行:`php artisan serve`(或 `composer dev`) 1. 确保后端服务运行:`php artisan serve`(或 `composer dev`)
2. 确保前端服务运行:`cd ui && pnpm dev`(开发端口 8001) 2. 确保前端服务运行:`cd ui && pnpm dev`(开发端口 8002)
3. 为测试角色分配新菜单权限(见 [3.8 权限节点配置](/zh/guide/permission.html)) 3. 为测试角色分配新菜单权限(见 [3.8 权限节点配置](/zh/guide/permission.html))
4. 刷新前端页面,侧边栏应出现"案例演示"菜单 4. 刷新前端页面,侧边栏应出现"案例演示"菜单
5. 点击菜单,测试增删改查功能 5. 点击菜单,测试增删改查功能
@@ -85,7 +85,7 @@ app/Http/Requests/ExampleRequest.php # 表单请求验证
app/Services/ExampleService.php # 业务服务 app/Services/ExampleService.php # 业务服务
app/Http/Controllers/Api/ExampleController.php # 控制器 app/Http/Controllers/Api/ExampleController.php # 控制器
routes/api.php # 路由(example 模块 9 条) routes/api.php # 路由(example 模块 9 条)
# 前端(独立 EleVue 仓库): # 前端(独立 AntdVue 仓库):
# ui/src/api/tool/example.ts # 前端 API 模块 # ui/src/api/tool/example.ts # 前端 API 模块
# ui/src/views/tool/example/ # 前端页面 # ui/src/views/tool/example/ # 前端页面
``` ```
@@ -123,5 +123,5 @@ Route::post('{模块}/import', [Api\XxxController::class, 'import']); //
php artisan route:list php artisan route:list
# 测试路由是否正确(需先登录获取 token,见 2.7 第一个接口调试) # 测试路由是否正确(需先登录获取 token,见 2.7 第一个接口调试)
curl http://localhost:8000/api/example/page -H "Authorization: Bearer <token>" curl http://admin.laravel13.antdvue/api/example/page -H "Authorization: Bearer <token>"
``` ```
@@ -8,9 +8,9 @@
## 软件说明 ## 软件说明
基于 Laravel 13、PHP 8.3、Vue3、TypeScript、Vite、ElementPlus 等技术栈构建的企业级前后端分离后台管理系统,支持 MySQL、PostgreSQL、SQL Server、Oracle、SQLite 等数据库。 基于 Laravel 13、PHP 8.3、Vue3、TypeScript、Vite、AntDesign 等技术栈构建的企业级前后端分离后台管理系统,支持 MySQL、PostgreSQL、SQL Server、Oracle、SQLite 等数据库。
后端采用 PHP 语言,基于 Laravel 13 框架、Eloquent ORM 数据库操作、PHP 8 Attribute 注解等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 ElementPlus 框架,遵循 Vue 前后端分离主流设计思想。系统以"声明式配置 + 基类钩子"为核心设计哲学:业务 Service 只声明"是什么"(属性),不重复实现"怎么做"(方法)。 后端采用 PHP 语言,基于 Laravel 13 框架、Eloquent ORM 数据库操作、PHP 8 Attribute 注解等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 AntDesign 框架,遵循 Vue 前后端分离主流设计思想。系统以"声明式配置 + 基类钩子"为核心设计哲学:业务 Service 只声明"是什么"(属性),不重复实现"怎么做"(方法)。
系统采用完全前后端分离架构,前端通过 API 调用后端服务实现数据交互。内置完善的 RBAC 权限体系,权限颗粒度精细至按钮级别;支持多租户数据隔离和数据权限控制。系统预置了用户、角色、菜单、部门、岗位、职级、日志、字典、配置、参数、定时任务、代码生成器等基础功能模块,开箱即用。 系统采用完全前后端分离架构,前端通过 API 调用后端服务实现数据交互。内置完善的 RBAC 权限体系,权限颗粒度精细至按钮级别;支持多租户数据隔离和数据权限控制。系统预置了用户、角色、菜单、部门、岗位、职级、日志、字典、配置、参数、定时任务、代码生成器等基础功能模块,开箱即用。
@@ -54,19 +54,27 @@
## 软件出处 ## 软件出处
+ 软件名称:RXThinkCMF前后端分离Laravel13+ElementPlus版本 + 软件名称:RXThinkCMF前后端分离Laravel13+AntDesign版本
+ 软件作者:@小蚂蚁云团队 + 软件作者:@小蚂蚁云团队
+ 软件协议:Apache-2.0 + 软件协议:Apache-2.0
+ 官网网址:[https://www.rxthink.cn](https://www.rxthink.cn) + 官网网址:[https://www.rxthink.cn](https://www.rxthink.cn)
+ 文档网址:[http://docs.laravel13.elevue.rxthink.cn](http://docs.laravel13.elevue.rxthink.cn) + 文档网址:[http://docs.laravel13.antdvue.rxthink.cn](http://docs.laravel13.antdvue.rxthink.cn)
+ 演示地址:[http://manage.laravel13.elevue.rxthink.cn](http://manage.laravel13.elevue.rxthink.cn) + 演示地址:[http://manage.laravel13.antdvue.rxthink.cn](http://manage.laravel13.antdvue.rxthink.cn)
## 软件版本 ## 软件版本
| 版本名称 | 说明 | 地址 | | 版本名称 | 说明 | 地址 |
|:---------|:-----|:-----| |:---------|:-----|:-----|
| ThinkPHP6+Layui混编版 | 基于 ThinkPHP6、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_Layui) |
| ThinkPHP6+ElementPlus前后端分离版 | 基于 ThinkPHP6、Vue3、ElementPlus 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue) |
| ThinkPHP6+AntDesign前后端分离版 | 基于 ThinkPHP6、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_AntdVue) |
| ThinkPHP6+NaiveUI前后端分离版 | 基于 ThinkPHP6、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue) |
| ThinkPHP8+Layui混编版 | 基于 ThinkPHP8、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_Layui) |
| ThinkPHP8+ElementPlus前后端分离版 | 基于 ThinkPHP8、Vue3、ElementPlus 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_EleVue) |
| ThinkPHP8+AntDesign前后端分离版 | 基于 ThinkPHP8、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue) |
| ThinkPHP8+NaiveUI前后端分离版 | 基于 ThinkPHP8、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_NaiveVue) |
| Laravel13+Layui混编版 | 基于 Laravel13、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_Layui) | | Laravel13+Layui混编版 | 基于 Laravel13、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_Layui) |
| **Laravel13+ElementPlus前后端分离版** | **基于 Laravel13、Vue3、ElementPlus 等技术栈(当前文档)** | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue) | | Laravel13+ElementPlus前后端分离版 | 基于 Laravel13、Vue3、ElementPlus 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue) |
| Laravel13+AntDesign前后端分离版 | 基于 Laravel13、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue) | | Laravel13+AntDesign前后端分离版 | 基于 Laravel13、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue) |
| Laravel13+NaiveUI前后端分离版 | 基于 Laravel13、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue) | | Laravel13+NaiveUI前后端分离版 | 基于 Laravel13、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue) |
@@ -153,15 +153,15 @@ Linux / macOS 服务器,最常用的定时任务触发方式。系统级 cron
crontab -e crontab -e
# 2. 添加定时任务(每分钟执行一次,推荐) # 2. 添加定时任务(每分钟执行一次,推荐)
* * * * * cd /www/RXThinkCMF_Laravel13_EleVue && /usr/bin/php artisan job:run >> /dev/null 2>&1 * * * * * cd /www/RXThinkCMF_Laravel13_AntdVue && /usr/bin/php artisan job:run >> /dev/null 2>&1
# 调试时改为写入日志文件 # 调试时改为写入日志文件
* * * * * cd /www/RXThinkCMF_Laravel13_EleVue && /usr/bin/php artisan job:run >> /var/log/job.log 2>&1 * * * * * cd /www/RXThinkCMF_Laravel13_AntdVue && /usr/bin/php artisan job:run >> /var/log/job.log 2>&1
# 3. 保存后验证 # 3. 保存后验证
crontab -l # 查看已配置的 cron crontab -l # 查看已配置的 cron
tail -f /var/log/syslog | grep CRON # 查看 cron 日志 tail -f /var/log/syslog | grep CRON # 查看 cron 日志
cd /www/RXThinkCMF_Laravel13_EleVue && php artisan job:run # 手动执行一次 cd /www/RXThinkCMF_Laravel13_AntdVue && php artisan job:run # 手动执行一次
``` ```
:::warning 路径注意 :::warning 路径注意
@@ -194,7 +194,7 @@ Windows Server 或本地开发环境(Laragon),通过任务计划程序定
5. 操作:启动程序 5. 操作:启动程序
程序或脚本:D:\laragon\bin\php\php-8.3.x-Win32-vs16-x64\php.exe 程序或脚本:D:\laragon\bin\php\php-8.3.x-Win32-vs16-x64\php.exe
添加参数:artisan job:run 添加参数:artisan job:run
起始于:D:\laragon\www\RXThinkCMF_Laravel13_EleVue 起始于:D:\laragon\www\RXThinkCMF_Laravel13_AntdVue
6. 勾选"不管用户是否登录都要运行" → 完成 6. 勾选"不管用户是否登录都要运行" → 完成
``` ```
@@ -202,7 +202,7 @@ Windows Server 或本地开发环境(Laragon),通过任务计划程序定
```powershell ```powershell
# 创建每分钟执行的任务(含工作目录与日志重定向) # 创建每分钟执行的任务(含工作目录与日志重定向)
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\laragon\www\RXThinkCMF_Laravel13_EleVue && D:\laragon\bin\php\php-8.3.x-Win32-vs16-x64\php.exe artisan job:run >> storage\job.log 2>&1" /sc minute /mo 1 /f schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\laragon\www\RXThinkCMF_Laravel13_AntdVue && D:\laragon\bin\php\php-8.3.x-Win32-vs16-x64\php.exe artisan job:run >> storage\job.log 2>&1" /sc minute /mo 1 /f
# 查询 / 手动运行 / 删除 # 查询 / 手动运行 / 删除
schtasks /query /tn "RXThinkCMF_Job" schtasks /query /tn "RXThinkCMF_Job"
@@ -300,8 +300,8 @@ php artisan job:daemon --stop
; /etc/supervisor/conf.d/rxthinkcmf-job.conf ; /etc/supervisor/conf.d/rxthinkcmf-job.conf
[program:rxthinkcmf-job] [program:rxthinkcmf-job]
command=php /www/RXThinkCMF_Laravel13_EleVue/artisan job:daemon command=php /www/RXThinkCMF_Laravel13_AntdVue/artisan job:daemon
directory=/www/RXThinkCMF_Laravel13_EleVue directory=/www/RXThinkCMF_Laravel13_AntdVue
autostart=true autostart=true
autorestart=true autorestart=true
user=www-data user=www-data
@@ -331,8 +331,8 @@ After=network.target mysql.service
[Service] [Service]
Type=simple Type=simple
User=www-data User=www-data
WorkingDirectory=/www/RXThinkCMF_Laravel13_EleVue WorkingDirectory=/www/RXThinkCMF_Laravel13_AntdVue
ExecStart=/usr/bin/php /www/RXThinkCMF_Laravel13_EleVue/artisan job:daemon ExecStart=/usr/bin/php /www/RXThinkCMF_Laravel13_AntdVue/artisan job:daemon
Restart=always Restart=always
RestartSec=10 RestartSec=10
@@ -57,7 +57,7 @@ feat(generator): 支持树形表格模板生成
- 新增 ui2/ 树形模板目录 - 新增 ui2/ 树形模板目录
- 自动识别 parent_id/pid 字段 - 自动识别 parent_id/pid 字段
- 支持 el-tree-select 组件 - 支持 a-tree-select 组件
Closes #123 Closes #123
``` ```
@@ -17,7 +17,7 @@
```bash ```bash
# 进入项目目录(以实际解压路径为准) # 进入项目目录(以实际解压路径为准)
cd RXThinkCMF_Laravel13_EleVue cd RXThinkCMF_Laravel13_AntdVue
``` ```
:::tip 温馨提示 :::tip 温馨提示
@@ -76,7 +76,7 @@ APP_DEBUG=true
DB_CONNECTION=mysql # 数据库类型(mysql / pgsql / sqlsrv / sqlite) DB_CONNECTION=mysql # 数据库类型(mysql / pgsql / sqlsrv / sqlite)
DB_HOST=127.0.0.1 # 数据库服务器地址 DB_HOST=127.0.0.1 # 数据库服务器地址
DB_PORT=3306 # 数据库端口 DB_PORT=3306 # 数据库端口
DB_DATABASE=rxthinkcmf.laravel13.elevue # 数据库名称(需提前创建) DB_DATABASE=rxthinkcmf.laravel13.antdvue # 数据库名称(需提前创建)
DB_USERNAME=root # 数据库用户名 DB_USERNAME=root # 数据库用户名
DB_PASSWORD=root # 数据库密码 DB_PASSWORD=root # 数据库密码
DB_PREFIX=laravel_ # 表前缀(数据库脚本使用 laravel_ 前缀,必须保留) DB_PREFIX=laravel_ # 表前缀(数据库脚本使用 laravel_ 前缀,必须保留)
@@ -118,7 +118,7 @@ composer dev
# 方式二:单独启动 API 服务(内置 PHP 开发服务器,默认监听 127.0.0.1:8000) # 方式二:单独启动 API 服务(内置 PHP 开发服务器,默认监听 127.0.0.1:8000)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# serve → 启动 Laravel 内置的 PHP 开发服务器 # serve → 启动 Laravel 内置的 PHP 开发服务器
# 启动后浏览器访问 http://localhost:8000 即可 # 启动后浏览器访问 http://admin.laravel13.antdvue 即可
php artisan serve php artisan serve
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -156,13 +156,13 @@ INFO Press Ctrl-C to quit.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 健康检查(Laravel 内置 /up 路由) # 健康检查(Laravel 内置 /up 路由)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/up curl http://admin.laravel13.antdvue/up
# → {"status":"ok"} # → {"status":"ok"}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 验证码接口(公开接口,同时验证 PHP → Redis 链路) # 验证码接口(公开接口,同时验证 PHP → Redis 链路)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/captcha curl http://admin.laravel13.antdvue/api/captcha
# → {"code":0,"ok":true,"msg":"操作成功","data":{"key":"...","captcha":"data:image/png;base64,..."}} # → {"code":0,"ok":true,"msg":"操作成功","data":{"key":"...","captcha":"data:image/png;base64,..."}}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -171,7 +171,7 @@ curl http://localhost:8000/api/captcha
# -X POST → 使用 POST 方法 # -X POST → 使用 POST 方法
# -H → 设置请求头(Content-Type: application/json) # -H → 设置请求头(Content-Type: application/json)
# -d → 发送 JSON 格式的请求体 # -d → 发送 JSON 格式的请求体
curl -X POST http://localhost:8000/api/login \ curl -X POST http://admin.laravel13.antdvue/api/login \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}' -d '{"username":"admin","password":"123456"}'
@@ -9,15 +9,15 @@
```text ```text
document/ document/
├── mysql/ ├── mysql/
│ └── rxthinkcmf.laravel13.elevue.sql # MySQL 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # MySQL 版本
├── postgresql/ ├── postgresql/
│ └── rxthinkcmf.laravel13.elevue.sql # PostgreSQL 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # PostgreSQL 版本
├── sqlserver/ ├── sqlserver/
│ └── rxthinkcmf.laravel13.elevue.sql # SQL Server 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # SQL Server 版本
├── oracle/ ├── oracle/
│ └── rxthinkcmf.laravel13.elevue.sql # Oracle 版本 │ └── rxthinkcmf.laravel13.antdvue.sql # Oracle 版本
└── sqlite/ └── sqlite/
└── rxthinkcmf.laravel13.elevue.sql # SQLite 版本 └── rxthinkcmf.laravel13.antdvue.sql # SQLite 版本
``` ```
各数据库脚本已针对对应的 SQL 语法做了适配,包含完整的表结构(25 张业务表,`laravel_` 前缀)与初始数据。 各数据库脚本已针对对应的 SQL 语法做了适配,包含完整的表结构(25 张业务表,`laravel_` 前缀)与初始数据。
@@ -41,7 +41,7 @@ mysql -u root -p
-- 创建项目数据库 -- 创建项目数据库
-- CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符 -- CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
-- COLLATE utf8mb4_general_ci → 不区分大小写的排序规则 -- COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
CREATE DATABASE `rxthinkcmf.laravel13.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE DATABASE `rxthinkcmf.laravel13.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
EXIT; EXIT;
``` ```
@@ -54,7 +54,7 @@ EXIT;
# -u root -proot → 数据库用户名和密码 # -u root -proot → 数据库用户名和密码
# rxthinkcmf... → 目标数据库名 # rxthinkcmf... → 目标数据库名
# < file.sql → 将 SQL 文件内容作为 mysql 命令的输入 # < file.sql → 将 SQL 文件内容作为 mysql 命令的输入
mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
:::tip 温馨提示 :::tip 温馨提示
@@ -68,7 +68,7 @@ mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.lar
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ # 必须保留,脚本中表均为 laravel_ 前缀 DB_PREFIX=laravel_ # 必须保留,脚本中表均为 laravel_ 前缀
@@ -89,10 +89,10 @@ sudo -u postgres psql
-- 创建项目数据库 -- 创建项目数据库
-- OWNER postgres → 指定数据库所有者 -- OWNER postgres → 指定数据库所有者
-- ENCODING 'UTF8' → 使用 UTF-8 编码 -- ENCODING 'UTF8' → 使用 UTF-8 编码
CREATE DATABASE "rxthinkcmf.laravel13.elevue" OWNER postgres ENCODING 'UTF8'; CREATE DATABASE "rxthinkcmf.laravel13.antdvue" OWNER postgres ENCODING 'UTF8';
-- 授予用户对该数据库的所有权限 -- 授予用户对该数据库的所有权限
GRANT ALL PRIVILEGES ON DATABASE "rxthinkcmf.laravel13.elevue" TO postgres; GRANT ALL PRIVILEGES ON DATABASE "rxthinkcmf.laravel13.antdvue" TO postgres;
\q \q
``` ```
@@ -105,7 +105,7 @@ GRANT ALL PRIVILEGES ON DATABASE "rxthinkcmf.laravel13.elevue" TO postgres;
# -U postgres → 连接用户 # -U postgres → 连接用户
# -d ... → 目标数据库 # -d ... → 目标数据库
# -f file.sql → 执行 SQL 文件 # -f file.sql → 执行 SQL 文件
psql -U postgres -d rxthinkcmf.laravel13.elevue -f document/postgresql/rxthinkcmf.laravel13.elevue.sql psql -U postgres -d rxthinkcmf.laravel13.antdvue -f document/postgresql/rxthinkcmf.laravel13.antdvue.sql
``` ```
### 3. 配置 .env ### 3. 配置 .env
@@ -114,7 +114,7 @@ psql -U postgres -d rxthinkcmf.laravel13.elevue -f document/postgresql/rxthinkcm
DB_CONNECTION=pgsql DB_CONNECTION=pgsql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=5432 DB_PORT=5432
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=postgres DB_USERNAME=postgres
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -146,7 +146,7 @@ touch database/database.sqlite
# sqlite3 → SQLite 命令行工具 # sqlite3 → SQLite 命令行工具
# 数据库文件路径 → SQLite 数据库存储在单个文件中 # 数据库文件路径 → SQLite 数据库存储在单个文件中
# < file.sql → 将 SQL 文件作为输入执行 # < file.sql → 将 SQL 文件作为输入执行
sqlite3 database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.elevue.sql sqlite3 database/database.sqlite < document/sqlite/rxthinkcmf.laravel13.antdvue.sql
``` ```
### 3. 配置 .env ### 3. 配置 .env
@@ -178,7 +178,7 @@ DB_PREFIX=laravel_
# -P root → 密码 # -P root → 密码
# -Q → 执行 SQL 查询后退出 # -Q → 执行 SQL 查询后退出
# COLLATE Chinese_PRC_CI_AS → 中文排序规则(不区分大小写) # COLLATE Chinese_PRC_CI_AS → 中文排序规则(不区分大小写)
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elevue] COLLATE Chinese_PRC_CI_AS" sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.antdvue] COLLATE Chinese_PRC_CI_AS"
``` ```
### 2. 导入 SQL 脚本 ### 2. 导入 SQL 脚本
@@ -189,7 +189,7 @@ sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elev
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -d rxthinkcmf... → 目标数据库 # -d rxthinkcmf... → 目标数据库
# -i file.sql → 执行 SQL 文件 # -i file.sql → 执行 SQL 文件
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.elevue -i document/sqlserver/rxthinkcmf.laravel13.elevue.sql sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.antdvue -i document/sqlserver/rxthinkcmf.laravel13.antdvue.sql
``` ```
### 3. 配置 .env ### 3. 配置 .env
@@ -198,7 +198,7 @@ sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.elevue -i document/sql
DB_CONNECTION=sqlsrv DB_CONNECTION=sqlsrv
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=1433 DB_PORT=1433
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=sa DB_USERNAME=sa
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -218,15 +218,15 @@ sqlplus system/root@localhost:1521/XE
-- DATAFILE → 数据文件的物理路径 -- DATAFILE → 数据文件的物理路径
-- SIZE 100M → 初始大小 100MB -- SIZE 100M → 初始大小 100MB
-- AUTOEXTEND ON NEXT 100M → 空间不足时自动扩展 100MB -- AUTOEXTEND ON NEXT 100M → 空间不足时自动扩展 100MB
CREATE TABLESPACE "rxthinkcmf_laravel13_elevue" CREATE TABLESPACE "rxthinkcmf_laravel13_antdvue"
DATAFILE '/u01/oradata/rxthinkcmf_laravel13_elevue.dbf' DATAFILE '/u01/oradata/rxthinkcmf_laravel13_antdvue.dbf'
SIZE 100M AUTOEXTEND ON NEXT 100M; SIZE 100M AUTOEXTEND ON NEXT 100M;
-- 创建数据库用户 -- 创建数据库用户
-- IDENTIFIED BY → 设置密码 -- IDENTIFIED BY → 设置密码
-- DEFAULT TABLESPACE → 指定默认表空间 -- DEFAULT TABLESPACE → 指定默认表空间
CREATE USER "root" IDENTIFIED BY "root" CREATE USER "root" IDENTIFIED BY "root"
DEFAULT TABLESPACE "rxthinkcmf_laravel13_elevue"; DEFAULT TABLESPACE "rxthinkcmf_laravel13_antdvue";
-- 授权 -- 授权
-- CONNECT → 连接权限 -- CONNECT → 连接权限
@@ -247,7 +247,7 @@ EXIT;
# sqlplus → Oracle 命令行工具 # sqlplus → Oracle 命令行工具
# root/root@localhost:1521/XE → 用户/密码@主机:端口/服务名 # root/root@localhost:1521/XE → 用户/密码@主机:端口/服务名
# @file.sql → 执行 SQL 文件 # @file.sql → 执行 SQL 文件
sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.laravel13.elevue.sql sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.laravel13.antdvue.sql
``` ```
### 3. 配置 .env ### 3. 配置 .env
@@ -256,7 +256,7 @@ sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.laravel13.elevue
DB_CONNECTION=oracle DB_CONNECTION=oracle
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=1521 DB_PORT=1521
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -280,7 +280,7 @@ DB_PREFIX=laravel_
php artisan --version php artisan --version
# 方式二:手动测试登录接口(POST 请求 + JSON 参数) # 方式二:手动测试登录接口(POST 请求 + JSON 参数)
curl http://localhost:8000/api/login -X POST \ curl http://admin.laravel13.antdvue/api/login -X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}' -d '{"username":"admin","password":"123456"}'
``` ```
@@ -292,13 +292,13 @@ curl http://localhost:8000/api/login -X POST \
# MySQL:查询用户表记录数(验证表是否创建成功且有数据) # MySQL:查询用户表记录数(验证表是否创建成功且有数据)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -e → 执行 SQL 后退出 # -e → 执行 SQL 后退出
mysql -u root -proot rxthinkcmf.laravel13.elevue -e "SELECT COUNT(*) FROM laravel_user;" mysql -u root -proot rxthinkcmf.laravel13.antdvue -e "SELECT COUNT(*) FROM laravel_user;"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# PostgreSQL:查询用户表记录数 # PostgreSQL:查询用户表记录数
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -c → 执行 SQL 命令 # -c → 执行 SQL 命令
psql -U postgres -d rxthinkcmf.laravel13.elevue -c "SELECT COUNT(*) FROM laravel_user;" psql -U postgres -d rxthinkcmf.laravel13.antdvue -c "SELECT COUNT(*) FROM laravel_user;"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# SQLite:查询用户表记录数 # SQLite:查询用户表记录数
@@ -309,7 +309,7 @@ sqlite3 database/database.sqlite "SELECT COUNT(*) FROM laravel_user;"
# SQL Server:查询用户表记录数 # SQL Server:查询用户表记录数
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -Q → 执行查询后退出 # -Q → 执行查询后退出
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.elevue -Q "SELECT COUNT(*) FROM laravel_user;" sqlcmd -S localhost -U sa -P root -d rxthinkcmf.laravel13.antdvue -Q "SELECT COUNT(*) FROM laravel_user;"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Oracle:查询用户表记录数(需先进入 SQL 命令行) # Oracle:查询用户表记录数(需先进入 SQL 命令行)
@@ -348,7 +348,7 @@ EXIT;
```bash ```bash
# --default-character-set=utf8mb4 → 指定字符集为 utf8mb4(支持 emoji) # --default-character-set=utf8mb4 → 指定字符集为 utf8mb4(支持 emoji)
mysql -u root -proot --default-character-set=utf8mb4 rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -u root -proot --default-character-set=utf8mb4 rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
### Q: 报 `Table 'xxx.users' doesn't exist` ### Q: 报 `Table 'xxx.users' doesn't exist`
@@ -368,13 +368,13 @@ DB_PREFIX=laravel_
# 先删除旧数据库再重建(⚠️ 会清空所有数据) # 先删除旧数据库再重建(⚠️ 会清空所有数据)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# DROP DATABASE IF EXISTS → 如果存在则删除 # DROP DATABASE IF EXISTS → 如果存在则删除
mysql -u root -proot -e "DROP DATABASE IF EXISTS \`rxthinkcmf.laravel13.elevue\`;" mysql -u root -proot -e "DROP DATABASE IF EXISTS \`rxthinkcmf.laravel13.antdvue\`;"
# 创建数据库(指定字符集) # 创建数据库(指定字符集)
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4;" mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4;"
# 重新导入 SQL # 重新导入 SQL
mysql -u root -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -u root -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
### Q: 导入很慢或中断 ### Q: 导入很慢或中断
@@ -7,7 +7,7 @@ Docker 方案适合快速体验项目功能,无需在本机手动安装 PHP、
## 架构说明 ## 架构说明
```text ```text
浏览器 → localhost:8000 → [Nginx 容器:80] → [PHP-FPM 容器:9000] → Laravel 应用 浏览器 → admin.laravel13.antdvue → [Nginx 容器:80] → [PHP-FPM 容器:9000] → Laravel 应用
↕ ↕
[MySQL 容器:3306] + [Redis 容器:6379] [MySQL 容器:3306] + [Redis 容器:6379]
``` ```
@@ -20,7 +20,7 @@ Docker 方案适合快速体验项目功能,无需在本机手动安装 PHP、
## 文件结构 ## 文件结构
```text ```text
RXThinkCMF_Laravel13_EleVue/ RXThinkCMF_Laravel13_AntdVue/
├── docker-compose.yml # 容器编排(项目根目录) ├── docker-compose.yml # 容器编排(项目根目录)
├── .dockerignore # 构建上下文排除(.env、vendor、SQL 等) ├── .dockerignore # 构建上下文排除(.env、vendor、SQL 等)
├── docker/ ├── docker/
@@ -51,7 +51,7 @@ composer setup
项目已内置完整的 Docker 配置文件(`docker-compose.yml`、`docker/Dockerfile`、`docker/nginx.conf`),无需手动创建,直接启动即可: 项目已内置完整的 Docker 配置文件(`docker-compose.yml`、`docker/Dockerfile`、`docker/nginx.conf`),无需手动创建,直接启动即可:
```bash ```bash
cd RXThinkCMF_Laravel13_EleVue cd RXThinkCMF_Laravel13_AntdVue
# 启动所有服务(首次会自动构建镜像,约 2-5 分钟) # 启动所有服务(首次会自动构建镜像,约 2-5 分钟)
docker compose up -d docker compose up -d
@@ -135,7 +135,7 @@ services:
- DB_CONNECTION=mysql - DB_CONNECTION=mysql
- DB_HOST=mysql - DB_HOST=mysql
- DB_PORT=3306 - DB_PORT=3306
- DB_DATABASE=${MYSQL_DATABASE:-rxthinkcmf.laravel13.elevue} - DB_DATABASE=${MYSQL_DATABASE:-rxthinkcmf.laravel13.antdvue}
- DB_USERNAME=root - DB_USERNAME=root
- DB_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123} - DB_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123}
@@ -151,7 +151,7 @@ services:
- JWT_SECRET=${JWT_SECRET:-your_random_jwt_secret_key_at_least_32_bytes_long} - JWT_SECRET=${JWT_SECRET:-your_random_jwt_secret_key_at_least_32_bytes_long}
# 文件访问域名(接口返回上传文件的完整地址) # 文件访问域名(接口返回上传文件的完整地址)
- FILE_DOMAIN_URL=${DOMAIN_URL:-http://localhost:8000} - FILE_DOMAIN_URL=${DOMAIN_URL:-http://admin.laravel13.antdvue}
# 跨域来源(快速体验默认放开) # 跨域来源(快速体验默认放开)
- CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*} - CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*}
@@ -177,10 +177,10 @@ services:
- "${MYSQL_PORT:-3306}:3306" # 宿主机端口(默认 3306),可通过环境变量覆盖 - "${MYSQL_PORT:-3306}:3306" # 宿主机端口(默认 3306),可通过环境变量覆盖
environment: environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123} # root 密码 MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123} # root 密码
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.laravel13.elevue} # 自动创建的数据库 MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.laravel13.antdvue} # 自动创建的数据库
volumes: volumes:
- mysql_data:/var/lib/mysql # 数据持久化 - mysql_data:/var/lib/mysql # 数据持久化
- ./document/mysql/rxthinkcmf.laravel13.elevue.sql:/docker-entrypoint-initdb.d/init.sql:ro # 初始化 SQL(仅首次启动执行) - ./document/mysql/rxthinkcmf.laravel13.antdvue.sql:/docker-entrypoint-initdb.d/init.sql:ro # 初始化 SQL(仅首次启动执行)
command: command:
- --character-set-server=utf8mb4 - --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci - --collation-server=utf8mb4_general_ci
@@ -425,9 +425,9 @@ NGINX_PORT=8000
MYSQL_PORT=3306 MYSQL_PORT=3306
REDIS_PORT=6379 REDIS_PORT=6379
# MySQL(首次启动自动导入 document/mysql/rxthinkcmf.laravel13.elevue.sql) # MySQL(首次启动自动导入 document/mysql/rxthinkcmf.laravel13.antdvue.sql)
MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024 MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024
MYSQL_DATABASE=rxthinkcmf.laravel13.elevue MYSQL_DATABASE=rxthinkcmf.laravel13.antdvue
# Redis # Redis
REDIS_PASSWORD=Your_Redis_Password_2024 REDIS_PASSWORD=Your_Redis_Password_2024
@@ -435,8 +435,8 @@ REDIS_PASSWORD=Your_Redis_Password_2024
# 应用 # 应用
JWT_SECRET=<32 字符以上随机值,openssl rand -base64 32 生成> JWT_SECRET=<32 字符以上随机值,openssl rand -base64 32 生成>
APP_DEBUG=false APP_DEBUG=false
DOMAIN_URL=http://localhost:8000 DOMAIN_URL=http://admin.laravel13.antdvue
CORS_ALLOW_ORIGIN=http://localhost:8000 CORS_ALLOW_ORIGIN=http://admin.laravel13.antdvue
``` ```
:::tip 与本地 .env 的关系 :::tip 与本地 .env 的关系
@@ -460,8 +460,8 @@ CORS_ALLOW_ORIGIN=http://localhost:8000
| 服务 | 地址 | 说明 | | 服务 | 地址 | 说明 |
|------|------|------| |------|------|------|
| 后端 API | http://localhost:8000 | Nginx → PHP-FPM → Laravel | | 后端 API | http://admin.laravel13.antdvue | Nginx → PHP-FPM → Laravel |
| 健康检查 | http://localhost:8000/up | Laravel 内置健康路由 | | 健康检查 | http://admin.laravel13.antdvue/up | Laravel 内置健康路由 |
| MySQL | localhost:3306 | 用户名 root,默认密码 root123 | | MySQL | localhost:3306 | 用户名 root,默认密码 root123 |
| Redis | localhost:6379 | 默认密码 redis123 | | Redis | localhost:6379 | 默认密码 redis123 |
@@ -469,15 +469,15 @@ CORS_ALLOW_ORIGIN=http://localhost:8000
```bash ```bash
# 健康检查 # 健康检查
curl http://localhost:8000/up curl http://admin.laravel13.antdvue/up
# 验证码(同时验证 PHP→Redis 链路,返回 code=0 即成功) # 验证码(同时验证 PHP→Redis 链路,返回 code=0 即成功)
curl http://localhost:8000/api/captcha curl http://admin.laravel13.antdvue/api/captcha
# → {"code":0,"ok":true,"msg":"操作成功","data":{...}} # → {"code":0,"ok":true,"msg":"操作成功","data":{...}}
# 登录获取 Token(默认管理员账号 admin / 123456,登录后立即修改) # 登录获取 Token(默认管理员账号 admin / 123456,登录后立即修改)
# 验证码参数 key/code 与 /api/captcha 响应字段一致 # 验证码参数 key/code 与 /api/captcha 响应字段一致
curl -X POST http://localhost:8000/api/login \ curl -X POST http://admin.laravel13.antdvue/api/login \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456","key":"<captcha_key>","code":"<验证码字符>"}' -d '{"username":"admin","password":"123456","key":"<captcha_key>","code":"<验证码字符>"}'
``` ```
@@ -532,10 +532,10 @@ docker compose exec mysql bash
# 数据库操作 # 数据库操作
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 导入 SQL(手动方式,首次启动已自动导入) # 导入 SQL(手动方式,首次启动已自动导入)
docker compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql docker compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
# 进入 MySQL 命令行 # 进入 MySQL 命令行
docker compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.laravel13.elevue docker compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.laravel13.antdvue
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Laravel Artisan 命令 # Laravel Artisan 命令
@@ -35,7 +35,7 @@ LOG_LEVEL=debug # 日志级别(生产改为 info)
DB_CONNECTION=mysql # 数据库类型(mysql / pgsql / sqlsrv / sqlite) DB_CONNECTION=mysql # 数据库类型(mysql / pgsql / sqlsrv / sqlite)
DB_HOST=127.0.0.1 # 数据库地址 DB_HOST=127.0.0.1 # 数据库地址
DB_PORT=3306 # 端口 DB_PORT=3306 # 端口
DB_DATABASE=rxthinkcmf.laravel13.elevue # 数据库名 DB_DATABASE=rxthinkcmf.laravel13.antdvue # 数据库名
DB_USERNAME=root # 用户名 DB_USERNAME=root # 用户名
DB_PASSWORD=root # 密码 DB_PASSWORD=root # 密码
DB_PREFIX=laravel_ # 表前缀(脚本使用 laravel_ 前缀,必须保留) DB_PREFIX=laravel_ # 表前缀(脚本使用 laravel_ 前缀,必须保留)
@@ -228,7 +228,7 @@ APP_DEBUG=true # 开启调试模式(异常返回真实
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_PREFIX=laravel_ DB_PREFIX=laravel_
CORS_ALLOW_ORIGIN=* # 允许所有来源(Vite 代理/直连均可用) CORS_ALLOW_ORIGIN=* # 允许所有来源(Vite 代理/直连均可用)
@@ -284,11 +284,11 @@ php artisan config:show jwt.secret # 查看解析后的配置值(调试
### `.env.development`(开发环境) ### `.env.development`(开发环境)
```bash ```bash
# API 接口基础路径(Vite 代理会将此路径的请求转发到后端) # API 接口基础路径(开发环境留空,由 VITE_PROXY 代理转发)
VITE_GLOB_API_URL=/api VITE_GLOB_API_URL=
# API 前缀(一般留空) # API 前缀(所有接口路径的前缀)
VITE_GLOB_API_URL_PREFIX= VITE_GLOB_API_URL_PREFIX=/api
# 是否启用 Mock 数据(false = 使用真实后端 API) # 是否启用 Mock 数据(false = 使用真实后端 API)
VITE_USE_MOCK=false VITE_USE_MOCK=false
@@ -297,11 +297,11 @@ VITE_USE_MOCK=false
### `.env.production`(生产环境) ### `.env.production`(生产环境)
```bash ```bash
# API 接口基础路径(生产环境的实际后端地址) # API 接口基础路径(留空 = 同域,由 Nginx 代理;跨域时填后端完整地址)
VITE_GLOB_API_URL=/api VITE_GLOB_API_URL=
# API 前缀 # API 前缀
VITE_GLOB_API_URL_PREFIX= VITE_GLOB_API_URL_PREFIX=/api
``` ```
在代码中访问环境变量: 在代码中访问环境变量:
@@ -441,7 +441,7 @@ MySQL 是项目默认推荐的关系型数据库,集成环境已自带,开
# 若前面有分号则去掉,保存后重启 Laragon # 若前面有分号则去掉,保存后重启 Laragon
# 创建数据库 # 创建数据库
D:\laragon\bin\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" D:\laragon\bin\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
``` ```
```sh [macOS] ```sh [macOS]
@@ -450,7 +450,7 @@ brew services start mysql
sudo mysql_secure_installation sudo mysql_secure_installation
# 创建数据库 # 创建数据库
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
``` ```
```sh [Ubuntu / Debian] ```sh [Ubuntu / Debian]
@@ -460,7 +460,7 @@ sudo systemctl enable mysql
sudo mysql_secure_installation sudo mysql_secure_installation
# 创建数据库 # 创建数据库
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
``` ```
```sh [Rocky Linux / CentOS] ```sh [Rocky Linux / CentOS]
@@ -470,7 +470,7 @@ sudo systemctl enable mysqld
sudo mysql_secure_installation sudo mysql_secure_installation
# 创建数据库 # 创建数据库
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
``` ```
::: :::
@@ -481,7 +481,7 @@ mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -507,7 +507,7 @@ PostgreSQL 功能强大,支持 JSONB、全文检索等高级特性,需单独
psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'root';" psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'root';"
# 创建数据库 # 创建数据库
psql -U postgres -c "CREATE DATABASE \"rxthinkcmf.laravel13.elevue\" OWNER postgres ENCODING 'UTF8';" psql -U postgres -c "CREATE DATABASE \"rxthinkcmf.laravel13.antdvue\" OWNER postgres ENCODING 'UTF8';"
``` ```
```sh [macOS] ```sh [macOS]
@@ -518,8 +518,8 @@ brew services start postgresql
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'root';" sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'root';"
# 创建数据库 # 创建数据库
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.elevue\" OWNER postgres ENCODING 'UTF8';" sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.antdvue\" OWNER postgres ENCODING 'UTF8';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.laravel13.elevue\" TO postgres;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.laravel13.antdvue\" TO postgres;"
``` ```
```sh [Ubuntu / Debian] ```sh [Ubuntu / Debian]
@@ -530,8 +530,8 @@ sudo systemctl start postgresql
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'root';" sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'root';"
# 创建数据库 # 创建数据库
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.elevue\" OWNER postgres ENCODING 'UTF8';" sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.antdvue\" OWNER postgres ENCODING 'UTF8';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.laravel13.elevue\" TO postgres;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.laravel13.antdvue\" TO postgres;"
``` ```
```sh [Rocky Linux / CentOS] ```sh [Rocky Linux / CentOS]
@@ -543,7 +543,7 @@ sudo systemctl start postgresql
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'root';" sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'root';"
# 创建数据库 # 创建数据库
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.elevue\" OWNER postgres ENCODING 'UTF8';" sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.antdvue\" OWNER postgres ENCODING 'UTF8';"
``` ```
::: :::
@@ -554,7 +554,7 @@ sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.laravel13.elevue\" OWNER
DB_CONNECTION=pgsql DB_CONNECTION=pgsql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=5432 DB_PORT=5432
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=postgres DB_USERNAME=postgres
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -592,7 +592,7 @@ DB_PREFIX=laravel_
``` ```
:::warning :::warning
SQLite 仅适合开发与测试。完整初始化数据请导入 `document/sqlite/rxthinkcmf.laravel13.elevue.sql`(五种数据库各有专用脚本,按 `document/<数据库类型>/` 分组,见 [2.5 数据库初始化](/zh/quickstart/database-init.html))。 SQLite 仅适合开发与测试。完整初始化数据请导入 `document/sqlite/rxthinkcmf.laravel13.antdvue.sql`(五种数据库各有专用脚本,按 `document/<数据库类型>/` 分组,见 [2.5 数据库初始化](/zh/quickstart/database-init.html))。
::: :::
### SQL Server 数据库 ### SQL Server 数据库
@@ -611,7 +611,7 @@ SQL Server 是微软商业数据库,Windows 下安装 ODBC 驱动后开箱即
# 完整 DLL 清单与选择规则见 7.6 数据库迁移工具 → 驱动安装与环境配置 # 完整 DLL 清单与选择规则见 7.6 数据库迁移工具 → 驱动安装与环境配置
# 创建数据库 # 创建数据库
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elevue] COLLATE Chinese_PRC_CI_AS" sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.antdvue] COLLATE Chinese_PRC_CI_AS"
``` ```
```sh [macOS] ```sh [macOS]
@@ -624,7 +624,7 @@ docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=root" \
pecl install pdo_sqlsrv pecl install pdo_sqlsrv
# 创建数据库 # 创建数据库
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elevue] COLLATE Chinese_PRC_CI_AS" sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.antdvue] COLLATE Chinese_PRC_CI_AS"
``` ```
```sh [Ubuntu / Debian] ```sh [Ubuntu / Debian]
@@ -646,7 +646,7 @@ echo "extension=pdo_sqlsrv.so" | sudo tee /etc/php/8.3/mods-available/pdo_sqlsrv
sudo phpenmod pdo_sqlsrv sudo phpenmod pdo_sqlsrv
# 创建数据库 # 创建数据库
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elevue] COLLATE Chinese_PRC_CI_AS" sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.antdvue] COLLATE Chinese_PRC_CI_AS"
``` ```
```sh [Rocky Linux / CentOS] ```sh [Rocky Linux / CentOS]
@@ -666,7 +666,7 @@ sudo pecl install pdo_sqlsrv
echo "extension=pdo_sqlsrv.so" | sudo tee /etc/php.d/pdo_sqlsrv.ini echo "extension=pdo_sqlsrv.so" | sudo tee /etc/php.d/pdo_sqlsrv.ini
# 创建数据库 # 创建数据库
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elevue] COLLATE Chinese_PRC_CI_AS" sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.antdvue] COLLATE Chinese_PRC_CI_AS"
``` ```
::: :::
@@ -677,7 +677,7 @@ sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.laravel13.elev
DB_CONNECTION=sqlsrv DB_CONNECTION=sqlsrv
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=1433 DB_PORT=1433
DB_DATABASE=rxthinkcmf.laravel13.elevue DB_DATABASE=rxthinkcmf.laravel13.antdvue
DB_USERNAME=sa DB_USERNAME=sa
DB_PASSWORD=root DB_PASSWORD=root
DB_PREFIX=laravel_ DB_PREFIX=laravel_
@@ -711,7 +711,7 @@ Oracle 是企业级商业数据库,需手动安装 Instant Client 和 pdo_oci
# DB_CONNECTION=oracle # DB_CONNECTION=oracle
# DB_HOST=127.0.0.1 # DB_HOST=127.0.0.1
# DB_PORT=1521 # DB_PORT=1521
# DB_DATABASE=rxthinkcmf.laravel13.elevue # DB_DATABASE=rxthinkcmf.laravel13.antdvue
# DB_USERNAME=root # DB_USERNAME=root
# DB_PASSWORD=root # DB_PASSWORD=root
``` ```
@@ -986,7 +986,8 @@ strict-ssl = false
| 依赖 | 版本 | 说明 | | 依赖 | 版本 | 说明 |
|------|------|------| |------|------|------|
| `vue` | ^3.4.27 | 核心框架 | | `vue` | ^3.4.27 | 核心框架 |
| `element-plus` | ^2.7.4 | UI 组件库 | | `ant-design-vue` | 3.2.20 | UI 组件库 |
| `@ant-design-vue/pro-layout` | ^3.2.5 | 后台管理布局 |
| `pinia` | ^2.1.7 | 状态管理 | | `pinia` | ^2.1.7 | 状态管理 |
| `vue-router` | ^4.3.2 | 路由管理 | | `vue-router` | ^4.3.2 | 路由管理 |
| `axios` | ^0.27.2 | HTTP 请求 | | `axios` | ^0.27.2 | HTTP 请求 |
@@ -996,7 +997,7 @@ strict-ssl = false
| `echarts` | ^5.5.0 | 图表库 | | `echarts` | ^5.5.0 | 图表库 |
| `tailwindcss` | ^3.4.3 | CSS 框架 | | `tailwindcss` | ^3.4.3 | CSS 框架 |
| `xlsx` | ^0.18.5 | Excel 处理 | | `xlsx` | ^0.18.5 | Excel 处理 |
| `@element-plus/icons-vue` | ^2.3.1 | Element Plus 图标 | | `@ant-design/icons-vue` | ^6.1.0 | AntDesign 图标 |
| `dayjs` | ^1.11.11 | 日期处理 | | `dayjs` | ^1.11.11 | 日期处理 |
| `lodash` | ^4.17.21 | 工具函数库 | | `lodash` | ^4.17.21 | 工具函数库 |
@@ -59,7 +59,7 @@ A: 检查以下几点:
1. MySQL 服务是否启动:`systemctl status mysql` 1. MySQL 服务是否启动:`systemctl status mysql`
2. `.env` 中的 `DB_*` 配置是否正确(`DB_HOST`、`DB_PORT`、`DB_USERNAME`、`DB_PASSWORD`、`DB_DATABASE`) 2. `.env` 中的 `DB_*` 配置是否正确(`DB_HOST`、`DB_PORT`、`DB_USERNAME`、`DB_PASSWORD`、`DB_DATABASE`)
3. 数据库是否已创建并导入 `document/mysql/rxthinkcmf.laravel13.elevue.sql`(本项目 migrations 为空,**不会自动建表**) 3. 数据库是否已创建并导入 `document/mysql/rxthinkcmf.laravel13.antdvue.sql`(本项目 migrations 为空,**不会自动建表**)
4. `DB_PREFIX` 是否为 `laravel_`(漏配会报 `Table 'xxx.users' doesn't exist`) 4. `DB_PREFIX` 是否为 `laravel_`(漏配会报 `Table 'xxx.users' doesn't exist`)
5. 原生测试连接:`mysql -h 127.0.0.1 -P 3306 -u root -p -e "SELECT 1;"` 5. 原生测试连接:`mysql -h 127.0.0.1 -P 3306 -u root -p -e "SELECT 1;"`
@@ -88,7 +88,7 @@ pnpm install # 重新安装所有依赖
A: 检查以下几点: A: 检查以下几点:
1. 后端服务是否启动(`curl http://localhost:8000/api/captcha` 验证) 1. 后端服务是否启动(`curl http://admin.laravel13.antdvue/api/captcha` 验证)
2. Vite 代理配置是否正确(`vite.config.ts` 中的 `target`,改完重启 `pnpm dev`) 2. Vite 代理配置是否正确(`vite.config.ts` 中的 `target`,改完重启 `pnpm dev`)
3. API 路径是否正确(应以 `/api` 开头) 3. API 路径是否正确(应以 `/api` 开头)
@@ -107,7 +107,7 @@ A: 打开浏览器控制台(F12)查看错误信息,常见原因:
A: 正常现象。本项目 `database/migrations/` 为空,**建表以 SQL 脚本为准**,`migrate` 只初始化迁移记录表。请手动导入: A: 正常现象。本项目 `database/migrations/` 为空,**建表以 SQL 脚本为准**,`migrate` 只初始化迁移记录表。请手动导入:
```bash ```bash
mysql -uroot -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -uroot -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
``` ```
详见 [2.5 数据库初始化](/zh/quickstart/database-init.html)。 详见 [2.5 数据库初始化](/zh/quickstart/database-init.html)。
@@ -16,7 +16,7 @@
```bash ```bash
# GET 请求获取验证码(返回 Base64 编码的图片 + 验证码 key) # GET 请求获取验证码(返回 Base64 编码的图片 + 验证码 key)
curl http://localhost:8000/api/captcha curl http://admin.laravel13.antdvue/api/captcha
``` ```
响应示例: 响应示例:
@@ -52,7 +52,7 @@ curl http://localhost:8000/api/captcha
# password → 密码 # password → 密码
# key → 步骤一返回的验证码 key(与 captcha 响应字段命名一致) # key → 步骤一返回的验证码 key(与 captcha 响应字段命名一致)
# code → 验证码图片上显示的字符 # code → 验证码图片上显示的字符
curl -X POST http://localhost:8000/api/login \ curl -X POST http://admin.laravel13.antdvue/api/login \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"username": "admin", "username": "admin",
@@ -100,7 +100,7 @@ curl -X POST http://localhost:8000/api/login \
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# pageNo=1\&pageSize=10 → 分页参数(\& 是 bash 转义,防止 & 被解释为后台运行符) # pageNo=1\&pageSize=10 → 分页参数(\& 是 bash 转义,防止 & 被解释为后台运行符)
# -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token # -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token
curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \ curl "http://admin.laravel13.antdvue/api/user/page?pageNo=1&pageSize=10" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
``` ```
@@ -136,33 +136,33 @@ curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 获取当前登录用户信息 # 获取当前登录用户信息
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/index/getUser \ curl http://admin.laravel13.antdvue/api/index/getUser \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 获取当前用户的菜单列表(用于前端动态路由) # 获取当前用户的菜单列表(用于前端动态路由)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/index/getMenus \ curl http://admin.laravel13.antdvue/api/index/getMenus \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 获取字典项(gender = 性别字典的编码) # 获取字典项(gender = 性别字典的编码)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/dict/item/getDictItemList/gender \ curl http://admin.laravel13.antdvue/api/dict/item/getDictItemList/gender \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 刷新 Token(access_token 过期后,用 refresh_token 换取新的令牌对) # 刷新 Token(access_token 过期后,用 refresh_token 换取新的令牌对)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# refresh 为公开接口(无需 access_token) # refresh 为公开接口(无需 access_token)
curl -X POST http://localhost:8000/api/refresh \ curl -X POST http://admin.laravel13.antdvue/api/refresh \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"refresh_token": "<your_refresh_token>"}' -d '{"refresh_token": "<your_refresh_token>"}'
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 登出(令牌即时失效,需携带 access_token) # 登出(令牌即时失效,需携带 access_token)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl -X POST http://localhost:8000/api/logout \ curl -X POST http://admin.laravel13.antdvue/api/logout \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
``` ```
@@ -224,7 +224,7 @@ HTTP 状态码为 200,前端据 body `code=401` 触发跳转登录。
## ApiFox 配置建议 ## ApiFox 配置建议
1. 创建环境,设置变量: 1. 创建环境,设置变量:
- `base_url` = `http://localhost:8000` - `base_url` = `http://admin.laravel13.antdvue`
- `token` = 登录后填入 access_token - `token` = 登录后填入 access_token
2. 在请求头中使用变量: 2. 在请求头中使用变量:
@@ -1,7 +1,7 @@
# 2.4 前端服务启动 # 2.4 前端服务启动
:::tip 概述 :::tip 概述
本章指导您启动前端开发服务器,包括依赖安装、环境配置和 Vite 开发服务器启动。前端为本仓库 `ui/` 目录内的 EleVue 管理后台(Vue3 + ElementPlus)。 本章指导您启动前端开发服务器,包括依赖安装、环境配置和 Vite 开发服务器启动。前端为本仓库 `ui/` 目录内的 AntdVue 管理后台(Vue3 + AntDesign)。
::: :::
## 步骤一:安装依赖 ## 步骤一:安装依赖
@@ -42,13 +42,14 @@ npm config set registry https://registry.npmmirror.com
```bash ```bash
# 服务端口 # 服务端口
VITE_PORT = 8001 VITE_PORT = 8002
# 公共基础路径 # 公共基础路径
VITE_PUBLIC_PATH = / VITE_PUBLIC_PATH = /
# 代理配置(JSON 数组格式:[["前缀","目标地址"]]) # 代理配置(JSON 数组格式:[["前缀","目标地址"]])
VITE_PROXY = [["/api","http://localhost:8000/api"]] # 默认指向 Laragon 虚拟主机;直连 artisan serve 时改为 [["/api","http://admin.laravel13.antdvue/api"]]
VITE_PROXY=[["/api","http://admin.laravel13.antdvue/api"]]
# API 接口基础路径(留空,由代理处理) # API 接口基础路径(留空,由代理处理)
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
@@ -56,11 +57,11 @@ VITE_GLOB_API_URL =
# API 前缀 # API 前缀
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api
# 上传接口地址 # 上传接口地址(留空,走同域/代理)
VITE_GLOB_UPLOAD_URL = /api/upload VITE_GLOB_UPLOAD_URL=
# 图片访问地址(附件域名,对应后端 FILE_DOMAIN_URL) # 图片前缀地址(留空,按后端 FILE_DOMAIN_URL 解析)
VITE_GLOB_IMG_URL = http://file.laravel13.elevue VITE_GLOB_IMG_URL=
# 是否启用 Mock 数据(false = 使用真实后端 API) # 是否启用 Mock 数据(false = 使用真实后端 API)
VITE_USE_MOCK = false VITE_USE_MOCK = false
@@ -81,14 +82,14 @@ VITE_GLOB_API_URL =
# API 前缀 # API 前缀
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api
# 上传接口地址 # 上传接口地址(留空,走同域)
VITE_GLOB_UPLOAD_URL = /api/upload VITE_GLOB_UPLOAD_URL=
# 图片访问地址 # 图片前缀地址(跨域部署时与后端 FILE_DOMAIN_URL 保持一致)
VITE_GLOB_IMG_URL = https://file.example.com VITE_GLOB_IMG_URL=
# 是否启用 gzip 压缩 # 构建压缩方式:none / gzip / brotli(当前默认 none)
VITE_BUILD_COMPRESS = gzip VITE_BUILD_COMPRESS = 'none'
# 压缩后是否删除原文件 # 压缩后是否删除原文件
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
@@ -111,8 +112,8 @@ npm run dev
```text ```text
VITE v3.2.x ready in 1234 ms VITE v3.2.x ready in 1234 ms
➜ Local: http://localhost:8001/ ➜ Local: http://localhost:8002/
➜ Network: http://192.168.1.100:8001/ ➜ Network: http://192.168.1.100:8002/
``` ```
## 步骤四:配置 API 代理 ## 步骤四:配置 API 代理
@@ -124,10 +125,10 @@ npm run dev
```typescript ```typescript
// ui/vite.config.ts // ui/vite.config.ts
server: { server: {
port: 8001, // 开发服务器端口(对应 VITE_PORT) port: 8002, // 开发服务器端口(对应 VITE_PORT)
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000/api', // 后端服务地址(对应 VITE_PROXY) target: 'http://admin.laravel13.antdvue/api', // 后端服务地址(对应 VITE_PROXY)
changeOrigin: true, // 开启跨域(修改请求头的 Origin) changeOrigin: true, // 开启跨域(修改请求头的 Origin)
rewrite: (path) => path, // 保持路径不变(不做路径替换) rewrite: (path) => path, // 保持路径不变(不做路径替换)
}, },
@@ -138,20 +139,20 @@ server: {
后端使用虚拟域名时(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html)),target 改为对应地址: 后端使用虚拟域名时(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html)),target 改为对应地址:
```typescript ```typescript
target: 'http://admin.laravel13.elevue/api', target: 'http://admin.laravel13.antdvue/api',
``` ```
确保后端服务已启动(`php artisan serve`),然后访问 `http://localhost:8001`。 确保后端服务已启动(`php artisan serve`),然后访问 `http://localhost:8002`。
## 步骤五:验证前后端联调 ## 步骤五:验证前后端联调
1. 打开浏览器访问 `http://localhost:8001` 1. 打开浏览器访问 `http://localhost:8002`
2. 应该看到登录页面 2. 应该看到登录页面
3. 输入默认账号 `admin` / `123456` 3. 输入默认账号 `admin` / `123456`
4. 登录成功后进入控制台页面 4. 登录成功后进入控制台页面
:::tip 代理原理 :::tip 代理原理
浏览器 → `http://localhost:8001/api/login` → Vite 代理 → `http://localhost:8000/api/login` → 后端处理 浏览器 → `http://localhost:8002/api/login` → Vite 代理 → `http://admin.laravel13.antdvue/api/login` → 后端处理
代理方式下前端无需关心跨域;若改为直连后端(`VITE_GLOB_API_URL` 配置完整地址),则依赖后端 CORS 配置(`.env` 中 `CORS_ALLOW_ORIGIN`)。 代理方式下前端无需关心跨域;若改为直连后端(`VITE_GLOB_API_URL` 配置完整地址),则依赖后端 CORS 配置(`.env` 中 `CORS_ALLOW_ORIGIN`)。
::: :::
@@ -190,7 +191,7 @@ rm -rf node_modules pnpm-lock.yaml
pnpm install pnpm install
``` ```
### 2. 端口 8001 被占用 ### 2. 端口 8002 被占用
修改 `.env.development` 中的 `VITE_PORT` 或 `vite.config.ts` 中的 `server.port`: 修改 `.env.development` 中的 `VITE_PORT` 或 `vite.config.ts` 中的 `server.port`:
@@ -206,7 +207,7 @@ server: {
```bash ```bash
# 确认后端在运行(直接请求后端验证码接口) # 确认后端在运行(直接请求后端验证码接口)
curl http://localhost:8000/api/captcha curl http://admin.laravel13.antdvue/api/captcha
# → {"code":0,...} 即后端正常 # → {"code":0,...} 即后端正常
``` ```
@@ -21,7 +21,7 @@
D:\laragon\www\ # 站点根目录(Apache 主 DocumentRoot) D:\laragon\www\ # 站点根目录(Apache 主 DocumentRoot)
D:\laragon\bin\apache\httpd-2.4.x-...\ # Apache 主目录(下称 $HTTPD_HOME) D:\laragon\bin\apache\httpd-2.4.x-...\ # Apache 主目录(下称 $HTTPD_HOME)
D:\laragon\bin\php\php-8.3.x-Win32-vs16-x64\ # PHP 8.3 目录(下称 $PHP_HOME) D:\laragon\bin\php\php-8.3.x-Win32-vs16-x64\ # PHP 8.3 目录(下称 $PHP_HOME)
D:\laragon\www\RXThinkCMF_Laravel13_EleVue\ # 项目根目录(下称 $PROJECT) D:\laragon\www\RXThinkCMF_Laravel13_AntdVue\ # 项目根目录(下称 $PROJECT)
``` ```
:::warning PATH 中的 php 版本陷阱 :::warning PATH 中的 php 版本陷阱
@@ -55,13 +55,13 @@ $PHP_HOME\php.exe -i | findstr "extension_dir"
编辑 `C:\Windows\System32\drivers\etc\hosts`(管理员权限): 编辑 `C:\Windows\System32\drivers\etc\hosts`(管理员权限):
```text ```text
127.0.0.1 admin.laravel13.elevue file.laravel13.elevue 127.0.0.1 admin.laravel13.antdvue file.laravel13.antdvue
``` ```
| 域名 | 用途 | | 域名 | 用途 |
|------|------| |------|------|
| `admin.laravel13.elevue` | 应用入口(DocumentRoot 指向项目 `public/`) | | `admin.laravel13.antdvue` | 应用入口(DocumentRoot 指向项目 `public/`) |
| `file.laravel13.elevue` | 附件目录(DocumentRoot 指向项目 `uploads/`) | | `file.laravel13.antdvue` | 附件目录(DocumentRoot 指向项目 `uploads/`) |
同系列项目域名格式为 `admin.<项目>.<前端品牌>`,如 `admin.laravel13.antdvue`、`admin.laravel13.naivevue`。 同系列项目域名格式为 `admin.<项目>.<前端品牌>`,如 `admin.laravel13.antdvue`、`admin.laravel13.naivevue`。
@@ -84,11 +84,11 @@ Include "conf/vhost/*.conf" # 手动配置
# 项目应用本地虚拟主机配置 # 项目应用本地虚拟主机配置
<VirtualHost *:80> <VirtualHost *:80>
ServerAdmin rxthinkcmf@163.com ServerAdmin rxthinkcmf@163.com
ServerName admin.laravel13.elevue ServerName admin.laravel13.antdvue
DocumentRoot "D:\laragon\www\RXThinkCMF_Laravel13_EleVue\public" DocumentRoot "D:\laragon\www\RXThinkCMF_Laravel13_AntdVue\public"
ErrorLog "logs/laravel13-error.log" ErrorLog "logs/laravel13-error.log"
CustomLog "logs/laravel13-access.log" common CustomLog "logs/laravel13-access.log" common
<Directory "D:\laragon\www\RXThinkCMF_Laravel13_EleVue\public"> <Directory "D:\laragon\www\RXThinkCMF_Laravel13_AntdVue\public">
AllowOverride All AllowOverride All
Require all granted Require all granted
</Directory> </Directory>
@@ -96,11 +96,11 @@ Include "conf/vhost/*.conf" # 手动配置
# 项目附件目录本地虚拟主机配置 # 项目附件目录本地虚拟主机配置
<VirtualHost *:80> <VirtualHost *:80>
ServerName file.laravel13.elevue ServerName file.laravel13.antdvue
DocumentRoot "D:\laragon\www\RXThinkCMF_Laravel13_EleVue\uploads" DocumentRoot "D:\laragon\www\RXThinkCMF_Laravel13_AntdVue\uploads"
ErrorLog "logs/laravel13-error.log" ErrorLog "logs/laravel13-error.log"
CustomLog "logs/laravel13-access.log" common CustomLog "logs/laravel13-access.log" common
<Directory "D:\laragon\www\RXThinkCMF_Laravel13_EleVue\uploads"> <Directory "D:\laragon\www\RXThinkCMF_Laravel13_AntdVue\uploads">
AllowOverride All AllowOverride All
Require all granted Require all granted
</Directory> </Directory>
@@ -267,9 +267,9 @@ $PHP_HOME\php.exe artisan tinker --execute="try { \Illuminate\Support\Facades\Re
□ $PHP_HOME\php.exe -m | findstr redis → redis □ $PHP_HOME\php.exe -m | findstr redis → redis
□ httpd.exe -t → Syntax OK □ httpd.exe -t → Syntax OK
□ tasklist 查看 redis-server.exe → 进程存在,6379 LISTENING □ tasklist 查看 redis-server.exe → 进程存在,6379 LISTENING
□ curl http://admin.laravel13.elevue/api/captcha □ curl http://admin.laravel13.antdvue/api/captcha
→ {"code":0,"ok":true,"msg":"操作成功","data":{...}} → {"code":0,"ok":true,"msg":"操作成功","data":{...}}
□ curl -X POST http://admin.laravel13.elevue/api/login □ curl -X POST http://admin.laravel13.antdvue/api/login
→ {"code":1,"ok":false,"msg":"参数验证失败","data":{...}} (路由已通) → {"code":1,"ok":false,"msg":"参数验证失败","data":{...}} (路由已通)
``` ```
@@ -142,21 +142,19 @@ class DeptService extends BaseTenantService
### 单条删除 ### 单条删除
```vue ```vue
<el-popconfirm title="确认删除该记录?" @confirm="handleDelete(row.id)"> <a-popconfirm title="确认删除该记录?" @confirm="handleDelete(row.id)">
<template #reference> <a-button danger size="small">删除</a-button>
<el-button type="danger" size="small">删除</el-button> </a-popconfirm>
</template>
</el-popconfirm>
``` ```
### 批量删除 ### 批量删除
```vue ```vue
<el-button <a-button
type="danger" danger
:disabled="!selectedIds.length" :disabled="!selectedIds.length"
@click="handleBatchDelete" @click="handleBatchDelete"
> >
批量删除 ({{ selectedIds.length }}) 批量删除 ({{ selectedIds.length }})
</el-button> </a-button>
``` ```
@@ -1,7 +1,7 @@
# 12.7 前端编码规范 # 12.7 前端编码规范
:::tip 概述 :::tip 概述
前端编码规范涵盖 Vue 组件、TypeScript、API 模块、样式和代码风格等方面。前端工程位于 `ui/` 目录(Vue3 + TypeScript + ElementPlus + Pinia)。 前端编码规范涵盖 Vue 组件、TypeScript、API 模块、样式和代码风格等方面。前端工程位于 `ui/` 目录(Vue3 + TypeScript + AntDesign + Pinia)。
::: :::
## Vue 组件规范 ## Vue 组件规范
@@ -21,13 +21,13 @@
## 前端技术栈 ## 前端技术栈
前端为本仓库 `ui/` 目录内的 EleVue 管理后台(Vue3 + ElementPlus),与本后端通过 API 对接: 前端为本仓库 `ui/` 目录内的 AntdVue 管理后台(Vue3 + AntDesign),与本后端通过 API 对接:
| 类别 | 技术选型 | 版本 | 说明 | | 类别 | 技术选型 | 版本 | 说明 |
|------|----------|------|------| |------|----------|------|------|
| 框架 | Vue | 3.4+ | 核心 UI 框架(Composition API) | | 框架 | Vue | 3.4+ | 核心 UI 框架(Composition API) |
| 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 | | 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 |
| UI 库 | ElementPlus | 2.7+ | 组件库 | | UI 库 | AntDesign Vue | 3.2+(实际 3.2.20) | 组件库 |
| 构建工具 | Vite | 3.2+(实际 ^3.2.10) | 开发服务器与构建 | | 构建工具 | Vite | 3.2+(实际 ^3.2.10) | 开发服务器与构建 |
| 状态管理 | Pinia | 2.1+ | 全局状态管理 | | 状态管理 | Pinia | 2.1+ | 全局状态管理 |
| 路由 | Vue Router | 4.3+ | 路由管理 | | 路由 | Vue Router | 4.3+ | 路由管理 |
@@ -48,7 +48,7 @@
```text ```text
┌─────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────┐
│ 前端(Vue3 + ElementPlus) │ │ 前端(Vue3 + AntDesign) │
│ 通过 API 调用后端服务,纯 JSON 交互 │ │ 通过 API 调用后端服务,纯 JSON 交互 │
└──────────────────────────┬──────────────────────────────┘ └──────────────────────────┬──────────────────────────────┘
│ HTTP (JSON) │ HTTP (JSON)
@@ -113,9 +113,9 @@ php artisan db:migrate [选项]
# --drop-target-first → 迁移前先删除目标库中的同名表(避免表已存在报错) # --drop-target-first → 迁移前先删除目标库中的同名表(避免表已存在报错)
php artisan db:migrate \ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \ --dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=postgres --dst-password=123456 \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=postgres --dst-password=123456 \
--drop-target-first --drop-target-first
``` ```
@@ -129,9 +129,9 @@ php artisan db:migrate \
# --dst-port=1433 → SQL Server 默认端口 # --dst-port=1433 → SQL Server 默认端口
php artisan db:migrate \ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=sqlsrv --dst-host=127.0.0.1 --dst-port=1433 \ --dst-type=sqlsrv --dst-host=127.0.0.1 --dst-port=1433 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=sa --dst-password=123456 \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=sa --dst-password=123456 \
--drop-target-first --drop-target-first
``` ```
@@ -145,8 +145,8 @@ php artisan db:migrate \
# --dst-database → SQLite 文件路径(文件不存在时自动创建) # --dst-database → SQLite 文件路径(文件不存在时自动创建)
php artisan db:migrate \ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=sqlite --dst-database=database/rxthinkcmf.laravel13.elevue.db --dst-type=sqlite --dst-database=database/rxthinkcmf.laravel13.antdvue.db
``` ```
### MySQL → Oracle ### MySQL → Oracle
@@ -158,9 +158,9 @@ php artisan db:migrate \
# --dst-type=oracle → 目标驱动为 Oracle(自动建用户 + 序列 + 触发器) # --dst-type=oracle → 目标驱动为 Oracle(自动建用户 + 序列 + 触发器)
php artisan db:migrate \ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=oracle --dst-host=127.0.0.1 --dst-port=1521 \ --dst-type=oracle --dst-host=127.0.0.1 --dst-port=1521 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=system --dst-password=123456 \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=system --dst-password=123456 \
--drop-target-first --drop-target-first
``` ```
@@ -186,9 +186,9 @@ php artisan db:migrate \
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
php artisan db:migrate \ php artisan db:migrate \
--src-type=pgsql --src-host=127.0.0.1 --src-port=5432 \ --src-type=pgsql --src-host=127.0.0.1 --src-port=5432 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=postgres --src-password=123456 \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=postgres --src-password=123456 \
--dst-type=mysql --dst-host=127.0.0.1 --dst-port=3306 \ --dst-type=mysql --dst-host=127.0.0.1 --dst-port=3306 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=root --dst-password=root \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=root --dst-password=root \
--drop-target-first --drop-target-first
``` ```
@@ -200,9 +200,9 @@ php artisan db:migrate \
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
php artisan db:migrate \ php artisan db:migrate \
--src-type=sqlsrv --src-host=127.0.0.1 --src-port=1433 \ --src-type=sqlsrv --src-host=127.0.0.1 --src-port=1433 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=sa --src-password=123456 \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=sa --src-password=123456 \
--dst-type=mysql --dst-host=127.0.0.1 --dst-port=3306 \ --dst-type=mysql --dst-host=127.0.0.1 --dst-port=3306 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=root --dst-password=root \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=root --dst-password=root \
--drop-target-first --drop-target-first
``` ```
@@ -215,9 +215,9 @@ php artisan db:migrate \
# --tables=laravel_user,laravel_role,laravel_menu → 只迁移这 3 张表 # --tables=laravel_user,laravel_role,laravel_menu → 只迁移这 3 张表
php artisan db:migrate \ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \ --dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=postgres --dst-password=123456 \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=postgres --dst-password=123456 \
--tables=laravel_user,laravel_role,laravel_menu \ --tables=laravel_user,laravel_role,laravel_menu \
--drop-target-first --drop-target-first
``` ```
@@ -229,17 +229,17 @@ php artisan db:migrate \
```bash ```bash
$ php artisan db:migrate \ $ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \ --dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=postgres --dst-password=123456 \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=postgres --dst-password=123456 \
--drop-target-first --drop-target-first
============================================================== ==============================================================
[迁移开始] 源: mysql:rxthinkcmf.laravel13.elevue → 目标: pgsql:rxthinkcmf.laravel13.elevue [迁移开始] 源: mysql:rxthinkcmf.laravel13.antdvue → 目标: pgsql:rxthinkcmf.laravel13.antdvue
[迁移开始] 待迁移 25 张表 [迁移开始] 待迁移 25 张表
============================================================== ==============================================================
[·] [1/5] 建目标库... [·] [1/5] 建目标库...
· 目标库 "rxthinkcmf.laravel13.elevue" 已就绪 · 目标库 "rxthinkcmf.laravel13.antdvue" 已就绪
[✓] [1/5] 建目标库 完成(耗时 0ms) [✓] [1/5] 建目标库 完成(耗时 0ms)
[·] [2/5] 建表结构(25 张表)... [·] [2/5] 建表结构(25 张表)...
[✓] [2/5] 建表结构 完成(耗时 440ms),25 张表,118 个索引,0 个外键 [✓] [2/5] 建表结构 完成(耗时 440ms),25 张表,118 个索引,0 个外键
@@ -271,13 +271,13 @@ $ php artisan db:migrate \
```bash ```bash
$ php artisan db:migrate \ $ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=oracle --dst-host=127.0.0.1 --dst-port=1521 \ --dst-type=oracle --dst-host=127.0.0.1 --dst-port=1521 \
--dst-database=rxthinkcmf --dst-username=system --dst-password=123456 \ --dst-database=rxthinkcmf --dst-username=system --dst-password=123456 \
--drop-target-first --drop-target-first
============================================================== ==============================================================
[迁移开始] 源: mysql:rxthinkcmf.laravel13.elevue → 目标: oracle:rxthinkcmf.laravel13.elevue [迁移开始] 源: mysql:rxthinkcmf.laravel13.antdvue → 目标: oracle:rxthinkcmf.laravel13.antdvue
============================================================== ==============================================================
[·] [1/5] 建目标库... [·] [1/5] 建目标库...
· 目标库 "rxthinkcmf" 已就绪(CREATE USER + GRANT) · 目标库 "rxthinkcmf" 已就绪(CREATE USER + GRANT)
@@ -314,9 +314,9 @@ $ php artisan db:migrate \
```bash ```bash
$ php artisan db:migrate \ $ php artisan db:migrate \
--src-type=mysql --src-host=127.0.0.1 --src-port=3306 \ --src-type=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-database=rxthinkcmf.laravel13.elevue --src-username=root --src-password=root \ --src-database=rxthinkcmf.laravel13.antdvue --src-username=root --src-password=root \
--dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \ --dst-type=pgsql --dst-host=127.0.0.1 --dst-port=5432 \
--dst-database=rxthinkcmf.laravel13.elevue --dst-username=postgres --dst-password=123456 \ --dst-database=rxthinkcmf.laravel13.antdvue --dst-username=postgres --dst-password=123456 \
--tables=laravel_user,laravel_role,laravel_menu \ --tables=laravel_user,laravel_role,laravel_menu \
--drop-target-first --drop-target-first
@@ -693,7 +693,7 @@ $PHP_HOME\php.exe -m | findstr sqlite
5. `--drop-target-first` 会删除目标表的全部数据(含索引、外键和注释),请谨慎使用 5. `--drop-target-first` 会删除目标表的全部数据(含索引、外键和注释),请谨慎使用
6. 大表迁移建议调大 `--batch-size`(如 5000)以提高效率 6. 大表迁移建议调大 `--batch-size`(如 5000)以提高效率
7. 迁移过程中如遇中断,已提交的 `--batch-size` 批次数据不会回滚,建议使用 `--drop-target-first` 重试 7. 迁移过程中如遇中断,已提交的 `--batch-size` 批次数据不会回滚,建议使用 `--drop-target-first` 重试
8. 含点号的数据库名(如 `rxthinkcmf.laravel13.elevue`)各驱动均已适配 8. 含点号的数据库名(如 `rxthinkcmf.laravel13.antdvue`)各驱动均已适配
9. 索引、外键和注释在建表阶段同步迁移,使用 `--drop-target-first` 重建时会一并重建 9. 索引、外键和注释在建表阶段同步迁移,使用 `--drop-target-first` 重建时会一并重建
10. 外键约束在所有表创建后统一添加,避免引用顺序问题 10. 外键约束在所有表创建后统一添加,避免引用顺序问题
11. 命令退出码:成功返回 0,失败或行数校验未通过返回 1(可用于 CI/CD 判断) 11. 命令退出码:成功返回 0,失败或行数校验未通过返回 1(可用于 CI/CD 判断)
@@ -721,7 +721,7 @@ A: 调大 `--batch-size`,如 `--batch-size=5000`。默认 1000 行一批,增
A: 会。所有驱动(MySQL / PostgreSQL / SQL Server / Oracle / SQLite)在目标库不存在时都会自动创建,无需手动建库。建库时会自动从源库读取 collation 并应用(仅当源与目标驱动相同时)。 A: 会。所有驱动(MySQL / PostgreSQL / SQL Server / Oracle / SQLite)在目标库不存在时都会自动创建,无需手动建库。建库时会自动从源库读取 collation 并应用(仅当源与目标驱动相同时)。
PostgreSQL 走维护库 `postgres` 执行 `CREATE DATABASE`,含点号的库名会自动加双引号(如 `"rxthinkcmf.laravel13.elevue"`);建库失败时抛出 `创建目标数据库失败: …` 并把原始异常保留在 previous 中。 PostgreSQL 走维护库 `postgres` 执行 `CREATE DATABASE`,含点号的库名会自动加双引号(如 `"rxthinkcmf.laravel13.antdvue"`);建库失败时抛出 `创建目标数据库失败: …` 并把原始异常保留在 previous 中。
### Q: 报 `could not find driver` 怎么办? ### Q: 报 `could not find driver` 怎么办?
@@ -763,7 +763,7 @@ A: Oracle 没有原生自增语法。迁移工具会自动为自增列创建序
### Q: 含点号的数据库名支持吗? ### Q: 含点号的数据库名支持吗?
A: 支持。如 `rxthinkcmf.laravel13.elevue` 这类含点号的库名,各驱动均已适配。 A: 支持。如 `rxthinkcmf.laravel13.antdvue` 这类含点号的库名,各驱动均已适配。
### Q: SQLite 作为目标有什么限制? ### Q: SQLite 作为目标有什么限制?
+20 -14
View File
@@ -195,7 +195,7 @@ public function export(): JsonResponse
"code": 0, "code": 0,
"ok": true, "ok": true,
"msg": "导出成功", "msg": "导出成功",
"data": "http://file.laravel13.elevue/temp/20261006/用户数据_20261006050143.xlsx" "data": "http://file.laravel13.antdvue/temp/20261006/用户数据_20261006050143.xlsx"
} }
``` ```
@@ -328,27 +328,33 @@ public function export(array $params = []): string
```vue ```vue
<template> <template>
<el-upload <a-upload
:action="'/api/example/import'" :action="'/api/example/import'"
:headers="{ Authorization: `Bearer ${token}` }" :headers="{ Authorization: `Bearer ${token}` }"
:on-success="handleImportSuccess" :show-upload-list="false"
accept=".xls,.xlsx" accept=".xls,.xlsx"
@change="handleChange"
> >
<el-button type="primary">导入</el-button> <a-button type="primary">导入</a-button>
</el-upload> </a-upload>
</template> </template>
<script setup> <script setup>
const handleImportSuccess = (response) => { import { message } from 'ant-design-vue';
if (response.code === 0) {
const { count, errors } = response.data; const handleChange = ({ file }) => {
if (errors.length === 0) { if (file.status === 'done') {
ElMessage.success(`成功导入 ${count} 条数据`); const response = file.response;
} else { if (response.code === 0) {
ElMessage.warning(`成功 ${count} 条,失败 ${errors.length} 条`); const { count, errors } = response.data;
// 可展开 errors 查看行号与原因 if (errors.length === 0) {
message.success(`成功导入 ${count} 条数据`);
} else {
message.warning(`成功 ${count} 条,失败 ${errors.length} 条`);
// 可展开 errors 查看行号与原因
}
refreshTable();
} }
refreshTable();
} }
}; };
</script> </script>
@@ -1,7 +1,7 @@
# 7.5 前端文件组件 # 7.5 前端文件组件
:::tip 概述 :::tip 概述
本节介绍前端项目(独立 EleVue 仓库)中的文件处理组件,包括上传组件(BasicUpload)、富文本编辑器(Editor)与 Excel 导入导出组件的使用方法。组件封装了 [7.1 文件上传全流程](/zh/tools/upload.html) 的接口调用。 本节介绍前端项目(独立 AntdVue 仓库)中的文件处理组件,包括上传组件(BasicUpload)、富文本编辑器(Editor)与 Excel 导入导出组件的使用方法。组件封装了 [7.1 文件上传全流程](/zh/tools/upload.html) 的接口调用。
::: :::
## Upload 上传组件 ## Upload 上传组件
@@ -72,7 +72,7 @@ const handleImportSuccess = (data) => {
``` ```
:::tip 前端解析 vs 后端导入 :::tip 前端解析 vs 后端导入
- **后端导入(推荐,走业务校验)**:`el-upload` 直传 `/api/{module}/import`,由 Service 逐行 `add()`(字典反查、唯一校验、事务),见 [7.4 数据导入导出](/zh/tools/excel.html); - **后端导入(推荐,走业务校验)**:`a-upload` 直传 `/api/{module}/import`,由 Service 逐行 `add()`(字典反查、唯一校验、事务),见 [7.4 数据导入导出](/zh/tools/excel.html);
- **前端解析**:`ImportExcel` 组件用 `xlsx` 库本地解析为数组,适合导入前预览、无业务校验的场景。 - **前端解析**:`ImportExcel` 组件用 `xlsx` 库本地解析为数组,适合导入前预览、无业务校验的场景。
::: :::
@@ -83,7 +83,7 @@ FILE_DOMAIN_URL=https://cdn.example.com
| 环境 | 域名示例 | | 环境 | 域名示例 |
|------|---------| |------|---------|
| 本地开发 | (留空,使用相对路径) | | 本地开发 | (留空,使用相对路径) |
| 虚拟域名开发 | `http://file.laravel13.elevue` | | 虚拟域名开发 | `http://file.laravel13.antdvue` |
| 测试环境 | `https://test-cdn.example.com` | | 测试环境 | `https://test-cdn.example.com` |
| 生产环境 | `https://cdn.example.com` 或对象存储域名 | | 生产环境 | `https://cdn.example.com` 或对象存储域名 |
@@ -247,35 +247,40 @@ function save_remote_file(string $url, string $saveDir = ''): string|false
## 前端集成 ## 前端集成
### Element Plus 上传组件 ### AntDesign 上传组件
```vue ```vue
<template> <template>
<el-upload <a-upload
:action="'/api/upload/uploadFile'" :action="'/api/upload/uploadFile'"
:data="{ type: 'image' }" :data="{ type: 'image' }"
:headers="{ Authorization: `Bearer ${token}` }" :headers="{ Authorization: `Bearer ${token}` }"
:on-success="handleUploadSuccess"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:show-upload-list="false"
@change="handleUploadChange"
> >
<el-button type="primary">上传图片</el-button> <a-button type="primary">上传图片</a-button>
</el-upload> </a-upload>
</template> </template>
<script setup> <script setup>
import { message } from 'ant-design-vue';
const token = localStorage.getItem('access_token'); const token = localStorage.getItem('access_token');
const beforeUpload = (file) => { const beforeUpload = (file) => {
const isLt10M = file.size / 1024 / 1024 < 10; const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) { if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB'); message.error('文件大小不能超过 10MB');
} }
return isLt10M; return isLt10M;
}; };
const handleUploadSuccess = (response) => { const handleUploadChange = ({ file }) => {
// 提交表单时保存相对路径(filePath)最稳妥 if (file.status === 'done' && file.response.code === 0) {
form.value.avatar = response.data.filePath; // 提交表单时保存相对路径(filePath)最稳妥
form.value.avatar = file.response.data.filePath;
}
}; };
</script> </script>
``` ```
+7 -7
View File
@@ -163,13 +163,13 @@ class ExampleController extends Controller
这些模块不仅可以直接使用,也是学习本框架开发模式的最佳参考(详见 [第 6 章 业务模块](/zh/modules/user.html))。 这些模块不仅可以直接使用,也是学习本框架开发模式的最佳参考(详见 [第 6 章 业务模块](/zh/modules/user.html))。
## Vue3 + ElementPlus 前端 ## Vue3 + AntDesign 前端
前端采用主流技术栈,开发体验优秀: 前端采用主流技术栈,开发体验优秀:
- **Vue3 Composition API**:更灵活的组件逻辑组织 - **Vue3 Composition API**:更灵活的组件逻辑组织
- **TypeScript**:类型安全,减少运行时错误 - **TypeScript**:类型安全,减少运行时错误
- **ElementPlus**:成熟的企业级 UI 组件库 - **AntDesign**:成熟的企业级 UI 组件库
- **Pinia**:轻量级状态管理 - **Pinia**:轻量级状态管理
- **Vite**:极速的开发服务器和构建工具 - **Vite**:极速的开发服务器和构建工具
- **动态路由**:菜单和路由由后端驱动,前端无需硬编码 - **动态路由**:菜单和路由由后端驱动,前端无需硬编码
@@ -180,19 +180,19 @@ class ExampleController extends Controller
```bash ```bash
# 1. 从官方网站 https://www.rxthink.cn 下载源码包并解压 # 1. 从官方网站 https://www.rxthink.cn 下载源码包并解压
cd RXThinkCMF_Laravel13_EleVue cd RXThinkCMF_Laravel13_AntdVue
# 2. 一键初始化(依赖 + .env + APP_KEY + 迁移 + 前端构建) # 2. 一键初始化(依赖 + .env + APP_KEY + 迁移 + 前端构建)
composer setup composer setup
# 3. 导入数据库(本项目 migrations 为空,以 SQL 脚本为准) # 3. 导入数据库(本项目 migrations 为空,以 SQL 脚本为准)
mysql -uroot -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -uroot -proot -e "CREATE DATABASE \`rxthinkcmf.laravel13.antdvue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
mysql -uroot -proot rxthinkcmf.laravel13.elevue < document/mysql/rxthinkcmf.laravel13.elevue.sql mysql -uroot -proot rxthinkcmf.laravel13.antdvue < document/mysql/rxthinkcmf.laravel13.antdvue.sql
# 4. 修改 .env 数据库配置后,启动后端(含队列 / 日志 / Vite) # 4. 修改 .env 数据库配置后,启动后端(含队列 / 日志 / Vite)
composer dev composer dev
# 5. 启动前端(独立 EleVue 仓库) # 5. 启动前端(独立 AntdVue 仓库)
cd ui && npm install && npm run dev cd ui && npm install && npm run dev
``` ```
@@ -233,7 +233,7 @@ DB_CONNECTION=mysql # MySQL(默认推荐)
- 后端升级至 Laravel 13 + PHP 8.3(本版本) - 后端升级至 Laravel 13 + PHP 8.3(本版本)
- 声明式 CRUD 体系持续增强,覆盖导入导出、定时任务、代码生成等场景 - 声明式 CRUD 体系持续增强,覆盖导入导出、定时任务、代码生成等场景
- 前端持续升级 Vue3 + ElementPlus 主流栈 - 前端持续升级 Vue3 + AntDesign 主流栈
- 代码生成器功能不断增强 - 代码生成器功能不断增强
- 安全机制持续完善(JWT fail-close 校验、演示模式、受信代理等) - 安全机制持续完善(JWT fail-close 校验、演示模式、受信代理等)
+8 -1
View File
@@ -36,8 +36,15 @@ features:
link: /zh/architecture/database-design.html link: /zh/architecture/database-design.html
linkText: 查看更多 linkText: 查看更多
rel: noreferrer rel: noreferrer
- title: 🚀 全新技术栈 - title: 🚀 全新技术栈
details: Laravel 13、PHP 8.2、Vue3、TypeScript、Vite、Pinia、ElementPlus 等技术栈,采用主流前后端分离架构实现。 details: Laravel 13、PHP 8.3、Vue3、TypeScript、Vite、Pinia、ElementPlus 等技术栈,采用主流前后端分离架构实现。
link: /zh/tech-stack.html link: /zh/tech-stack.html
linkText: 查看更多 linkText: 查看更多
rel: noreferrer rel: noreferrer
@@ -164,11 +164,11 @@ Authorization: Bearer <access_token>
```bash ```bash
# curl # curl
curl http://localhost:8000/api/user/page \ curl http://admin.laravel13.elevue/api/user/page \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
# curl(带分页参数) # curl(带分页参数)
curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \ curl "http://admin.laravel13.elevue/api/user/page?pageNo=1&pageSize=10" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
``` ```
@@ -32,7 +32,7 @@
"ok": true, "ok": true,
"msg": "上传成功", "msg": "上传成功",
"data": { "data": {
"fileUrl": "http://localhost:8000/uploads/temp/20261002/123456_a1b2c3d4e5f6.jpg", "fileUrl": "http://admin.laravel13.elevue/uploads/temp/20261002/123456_a1b2c3d4e5f6.jpg",
"filePath": "temp/20261002/123456_a1b2c3d4e5f6.jpg", "filePath": "temp/20261002/123456_a1b2c3d4e5f6.jpg",
"originalName": "photo.jpg", "originalName": "photo.jpg",
"fileSize": 102400, "fileSize": 102400,
@@ -47,7 +47,7 @@
| 字段 | 类型 | 说明 | 示例 | | 字段 | 类型 | 说明 | 示例 |
|------|------|------|------| |------|------|------|------|
| `fileUrl` | string | 可访问 URL(`FILE_DOMAIN_URL` 为空时等于相对路径) | `http://localhost:8000/uploads/temp/...` | | `fileUrl` | string | 可访问 URL(`FILE_DOMAIN_URL` 为空时等于相对路径) | `http://admin.laravel13.elevue/uploads/temp/...` |
| `filePath` | string | 相对存储路径(**提交表单建议用此字段**) | `temp/20261002/123456_a1b2....jpg` | | `filePath` | string | 相对存储路径(**提交表单建议用此字段**) | `temp/20261002/123456_a1b2....jpg` |
| `originalName` | string | 原始文件名 | `photo.jpg` | | `originalName` | string | 原始文件名 | `photo.jpg` |
| `fileSize` | number | 文件大小(字节) | `102400` | | `fileSize` | number | 文件大小(字节) | `102400` |
@@ -167,7 +167,7 @@ const res = await http.request({
headers: { 'Content-Type': 'multipart/form-data' }, headers: { 'Content-Type': 'multipart/form-data' },
}); });
// res.filePath = "temp/20261002/123456_a1b2....jpg" // res.filePath = "temp/20261002/123456_a1b2....jpg"
// res.fileUrl = "http://localhost:8000/uploads/temp/20261002/..." // res.fileUrl = "http://admin.laravel13.elevue/uploads/temp/20261002/..."
// 上传图片(指定 type=image) // 上传图片(指定 type=image)
const res2 = await http.request({ const res2 = await http.request({
@@ -71,7 +71,7 @@
### 1. 前后端分离 ### 1. 前后端分离
前端 Vue3 应用通过 HTTP API 调用后端 Laravel 服务,两者独立开发、独立部署。前端开发阶段通过 Vite 的 `proxy` 配置将 `/api` 请求代理到后端 `http://localhost:8000`,生产环境由 Nginx 统一反向代理。 前端 Vue3 应用通过 HTTP API 调用后端 Laravel 服务,两者独立开发、独立部署。前端开发阶段通过 Vite 的 `proxy` 配置将 `/api` 请求代理到后端 `http://admin.laravel13.elevue`,生产环境由 Nginx 统一反向代理。
### 2. 分层架构 ### 2. 分层架构
@@ -43,7 +43,7 @@ location / {
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000', target: 'http://admin.laravel13.elevue',
changeOrigin: true, changeOrigin: true,
}, },
}, },
@@ -257,7 +257,7 @@ data.forEach((group) => {
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 批量保存网站配置(key 为配置项编码) # 批量保存网站配置(key 为配置项编码)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl -X POST http://localhost:8000/api/config/web/save \ curl -X POST http://admin.laravel13.elevue/api/config/web/save \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \ -H "Authorization: Bearer <token>" \
-d '{ -d '{
+1 -1
View File
@@ -353,7 +353,7 @@ php artisan config:cache
## 环境配置建议 ## 环境配置建议
```ini ```ini
# 开发环境:允许所有来源(前端 localhost:8001,后端 localhost:8000) # 开发环境:允许所有来源(前端 localhost:8001,后端 admin.laravel13.elevue)
CORS_ALLOW_ORIGIN=* CORS_ALLOW_ORIGIN=*
# 测试环境:指定测试域名,提前验证跨域配置 # 测试环境:指定测试域名,提前验证跨域配置
@@ -64,7 +64,7 @@ php artisan route:list --path=user/add --json
# → "middleware": ["api", "auth.jwt", "tenant", "demo", "log.operation"] # → "middleware": ["api", "auth.jwt", "tenant", "demo", "log.operation"]
# ③ 拦截生效(携带有效 token 发起写操作,期望 HTTP 403) # ③ 拦截生效(携带有效 token 发起写操作,期望 HTTP 403)
curl -X POST http://localhost:8000/api/user/add \ curl -X POST http://admin.laravel13.elevue/api/user/add \
-H "Authorization: Bearer {access_token}" \ -H "Authorization: Bearer {access_token}" \
-H "Accept: application/json" \ -H "Accept: application/json" \
-d '{"username":"demo"}' -d '{"username":"demo"}'
+3 -3
View File
@@ -191,7 +191,7 @@ INFO Server running on [http://127.0.0.1:8000].
``` ```
:::tip 温馨提示 :::tip 温馨提示
打开浏览器访问 `http://localhost:8000/` 可验证后端服务是否正常启动;健康检查接口为 `http://localhost:8000/up`。 打开浏览器访问 `http://admin.laravel13.elevue/` 可验证后端服务是否正常启动;健康检查接口为 `http://admin.laravel13.elevue/up`。
::: :::
## 第四步:前端启动 ## 第四步:前端启动
@@ -247,7 +247,7 @@ pnpm dev
``` ```
:::tip 温馨提示 :::tip 温馨提示
前端默认运行在 `8001` 端口,后端默认 `8000` 端口。Vite 开发代理由 `ui/.env.development` 的 `VITE_PROXY` 决定(默认转发到 `http://admin.laravel13.elevue/api`;直连 `php artisan serve` 时改为 `http://localhost:8000/api`,改后重启生效)。登录后请立即修改默认密码。 前端默认运行在 `8001` 端口,后端默认 `8000` 端口。Vite 开发代理由 `ui/.env.development` 的 `VITE_PROXY` 决定(默认转发到 `http://admin.laravel13.elevue/api`;直连 `php artisan serve` 时改为 `http://admin.laravel13.elevue/api`,改后重启生效)。登录后请立即修改默认密码。
::: :::
## 第五步:功能验证 ## 第五步:功能验证
@@ -267,7 +267,7 @@ pnpm dev
也可直接用命令行验证接口链路(验证码接口同时验证 PHP → Redis 链路): 也可直接用命令行验证接口链路(验证码接口同时验证 PHP → Redis 链路):
```bash ```bash
curl http://localhost:8000/api/captcha curl http://admin.laravel13.elevue/api/captcha
# → {"code":0,"ok":true,"msg":"操作成功","data":{...}} # → {"code":0,"ok":true,"msg":"操作成功","data":{...}}
``` ```
@@ -347,7 +347,7 @@ services:
# JWT 密钥 / 文件域名 / 跨域(取自 --env-file) # JWT 密钥 / 文件域名 / 跨域(取自 --env-file)
- JWT_SECRET=${JWT_SECRET} - JWT_SECRET=${JWT_SECRET}
- FILE_DOMAIN_URL=${DOMAIN_URL:-http://localhost:8000} - FILE_DOMAIN_URL=${DOMAIN_URL:-http://admin.laravel13.elevue}
- CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*} - CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*}
depends_on: depends_on:
@@ -497,7 +497,7 @@ docker compose logs -f app
# 6. 验证 # 6. 验证
curl https://your-domain/up curl https://your-domain/up
curl http://localhost:8000/api/captcha # 返回 code=0 即成功 curl http://admin.laravel13.elevue/api/captcha # 返回 code=0 即成功
``` ```
### 首次初始化与数据迁移 ### 首次初始化与数据迁移
@@ -286,7 +286,7 @@ sudo chown -R www-data:www-data /www/api/public/uploads/
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token # -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token
# <token> 替换为实际的 access_token 值 # <token> 替换为实际的 access_token 值
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/index/getUser curl -H "Authorization: Bearer <token>" http://admin.laravel13.elevue/api/index/getUser
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 检查 JWT 密钥配置(缺失或过短会 fail-close,拒绝签发/校验令牌) # 检查 JWT 密钥配置(缺失或过短会 fail-close,拒绝签发/校验令牌)
@@ -431,7 +431,7 @@ cd /www/api && php artisan job:daemon --stop
# 测试后端路由是否正常(绕过前端,直接请求 API) # 测试后端路由是否正常(绕过前端,直接请求 API)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -H → 设置请求头(携带 JWT Token) # -H → 设置请求头(携带 JWT Token)
curl http://localhost:8000/api/example/page -H "Authorization: Bearer <token>" curl http://admin.laravel13.elevue/api/example/page -H "Authorization: Bearer <token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 检查 Nginx 配置语法是否正确 # 检查 Nginx 配置语法是否正确
@@ -57,7 +57,7 @@ VITE_GLOB_API_URL_PREFIX = /api
``` ```
:::tip 代理目标地址 :::tip 代理目标地址
`VITE_PROXY` 当前默认指向 Laragon 虚拟主机 `http://admin.laravel13.elevue/api`;使用 `php artisan serve` 时改为直连 `http://localhost:8000/api`(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html))。修改后需重启 `pnpm dev`。 `VITE_PROXY` 当前默认指向 Laragon 虚拟主机 `http://admin.laravel13.elevue/api`;使用 `php artisan serve` 时改为直连 `http://admin.laravel13.elevue/api`(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html))。修改后需重启 `pnpm dev`。
::: :::
## 生产环境配置 ## 生产环境配置
@@ -124,7 +124,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
VITE_PROXY=[["/api","http://admin.laravel13.elevue/api"]] VITE_PROXY=[["/api","http://admin.laravel13.elevue/api"]]
# 直连 artisan serve 时的写法 # 直连 artisan serve 时的写法
VITE_PROXY=[["/api","http://localhost:8000/api"]] VITE_PROXY=[["/api","http://admin.laravel13.elevue/api"]]
``` ```
**格式:** JSON 数组,每项为 `[前缀, 目标地址]` **格式:** JSON 数组,每项为 `[前缀, 目标地址]`
@@ -134,7 +134,7 @@ VITE_PROXY=[["/api","http://localhost:8000/api"]]
**多代理示例:** **多代理示例:**
```bash ```bash
VITE_PROXY = [["/api","http://localhost:8000/api"],["/uploads","http://localhost:8000/uploads"]] VITE_PROXY = [["/api","http://admin.laravel13.elevue/api"],["/uploads","http://admin.laravel13.elevue/uploads"]]
``` ```
### VITE_GLOB_API_URL_PREFIX — API 前缀 ### VITE_GLOB_API_URL_PREFIX — API 前缀
@@ -246,7 +246,7 @@ export function createProxy(list: ProxyList = []) {
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000/api', // 目标地址(VITE_PROXY 驱动,含 /api 前缀) target: 'http://admin.laravel13.elevue/api', // 目标地址(VITE_PROXY 驱动,含 /api 前缀)
changeOrigin: true, // 修改请求头 Origin changeOrigin: true, // 修改请求头 Origin
ws: true, // 支持 WebSocket ws: true, // 支持 WebSocket
rewrite: (path) => path.replace(new RegExp('^/api'), ''), // 去掉前缀,由目标地址补回 rewrite: (path) => path.replace(new RegExp('^/api'), ''), // 去掉前缀,由目标地址补回
@@ -262,7 +262,7 @@ server: {
│ │
▼ Vite proxy ▼ Vite proxy
│ │
▼ 转发到: http://localhost:8000/api/user/page ▼ 转发到: http://admin.laravel13.elevue/api/user/page
``` ```
## 构建命令 ## 构建命令
@@ -376,7 +376,7 @@ pnpm build:prod # 生产环境构建
**原因:** 代理配置错误或后端未启动。 **原因:** 代理配置错误或后端未启动。
**解决:** 检查 `.env.development` 中 `VITE_PROXY` 的目标地址是否正确,`curl http://localhost:8000/api/captcha` 验证后端;改完配置重启 `pnpm dev`。 **解决:** 检查 `.env.development` 中 `VITE_PROXY` 的目标地址是否正确,`curl http://admin.laravel13.elevue/api/captcha` 验证后端;改完配置重启 `pnpm dev`。
### 问题 2:生产环境 API 请求跨域 ### 问题 2:生产环境 API 请求跨域
@@ -154,7 +154,7 @@
# GET /api/generator/preview # GET /api/generator/preview
# Content-Type: application/json # Content-Type: application/json
# Authorization: Bearer <token> # Authorization: Bearer <token>
curl -X GET http://localhost:8000/api/generator/preview \ curl -X GET http://admin.laravel13.elevue/api/generator/preview \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \ -H "Authorization: Bearer <token>" \
-d '{ -d '{
@@ -123,5 +123,5 @@ Route::post('{模块}/import', [Api\XxxController::class, 'import']); //
php artisan route:list php artisan route:list
# 测试路由是否正确(需先登录获取 token,见 2.7 第一个接口调试) # 测试路由是否正确(需先登录获取 token,见 2.7 第一个接口调试)
curl http://localhost:8000/api/example/page -H "Authorization: Bearer <token>" curl http://admin.laravel13.elevue/api/example/page -H "Authorization: Bearer <token>"
``` ```
@@ -65,8 +65,16 @@
| 版本名称 | 说明 | 地址 | | 版本名称 | 说明 | 地址 |
|:---------|:-----|:-----| |:---------|:-----|:-----|
| ThinkPHP6+Layui混编版 | 基于 ThinkPHP6、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_Layui) |
| ThinkPHP6+ElementPlus前后端分离版 | 基于 ThinkPHP6、Vue3、ElementPlus 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue) |
| ThinkPHP6+AntDesign前后端分离版 | 基于 ThinkPHP6、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_AntdVue) |
| ThinkPHP6+NaiveUI前后端分离版 | 基于 ThinkPHP6、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue) |
| ThinkPHP8+Layui混编版 | 基于 ThinkPHP8、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_Layui) |
| ThinkPHP8+ElementPlus前后端分离版 | 基于 ThinkPHP8、Vue3、ElementPlus 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_EleVue) |
| ThinkPHP8+AntDesign前后端分离版 | 基于 ThinkPHP8、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue) |
| ThinkPHP8+NaiveUI前后端分离版 | 基于 ThinkPHP8、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_NaiveVue) |
| Laravel13+Layui混编版 | 基于 Laravel13、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_Layui) | | Laravel13+Layui混编版 | 基于 Laravel13、Layui 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_Layui) |
| **Laravel13+ElementPlus前后端分离版** | **基于 Laravel13、Vue3、ElementPlus 等技术栈(当前文档)** | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue) | | Laravel13+ElementPlus前后端分离版 | 基于 Laravel13、Vue3、ElementPlus 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue) |
| Laravel13+AntDesign前后端分离版 | 基于 Laravel13、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue) | | Laravel13+AntDesign前后端分离版 | 基于 Laravel13、Vue3、AntDesign 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_AntdVue) |
| Laravel13+NaiveUI前后端分离版 | 基于 Laravel13、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue) | | Laravel13+NaiveUI前后端分离版 | 基于 Laravel13、Vue3、NaiveUI 等技术栈 | [Gitee地址](https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue) |
@@ -118,7 +118,7 @@ composer dev
# 方式二:单独启动 API 服务(内置 PHP 开发服务器,默认监听 127.0.0.1:8000) # 方式二:单独启动 API 服务(内置 PHP 开发服务器,默认监听 127.0.0.1:8000)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# serve → 启动 Laravel 内置的 PHP 开发服务器 # serve → 启动 Laravel 内置的 PHP 开发服务器
# 启动后浏览器访问 http://localhost:8000 即可 # 启动后浏览器访问 http://admin.laravel13.elevue 即可
php artisan serve php artisan serve
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -156,13 +156,13 @@ INFO Press Ctrl-C to quit.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 健康检查(Laravel 内置 /up 路由) # 健康检查(Laravel 内置 /up 路由)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/up curl http://admin.laravel13.elevue/up
# → {"status":"ok"} # → {"status":"ok"}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 验证码接口(公开接口,同时验证 PHP → Redis 链路) # 验证码接口(公开接口,同时验证 PHP → Redis 链路)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/captcha curl http://admin.laravel13.elevue/api/captcha
# → {"code":0,"ok":true,"msg":"操作成功","data":{"key":"...","captcha":"data:image/png;base64,..."}} # → {"code":0,"ok":true,"msg":"操作成功","data":{"key":"...","captcha":"data:image/png;base64,..."}}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -171,7 +171,7 @@ curl http://localhost:8000/api/captcha
# -X POST → 使用 POST 方法 # -X POST → 使用 POST 方法
# -H → 设置请求头(Content-Type: application/json) # -H → 设置请求头(Content-Type: application/json)
# -d → 发送 JSON 格式的请求体 # -d → 发送 JSON 格式的请求体
curl -X POST http://localhost:8000/api/login \ curl -X POST http://admin.laravel13.elevue/api/login \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}' -d '{"username":"admin","password":"123456"}'
@@ -280,7 +280,7 @@ DB_PREFIX=laravel_
php artisan --version php artisan --version
# 方式二:手动测试登录接口(POST 请求 + JSON 参数) # 方式二:手动测试登录接口(POST 请求 + JSON 参数)
curl http://localhost:8000/api/login -X POST \ curl http://admin.laravel13.elevue/api/login -X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}' -d '{"username":"admin","password":"123456"}'
``` ```
@@ -7,7 +7,7 @@ Docker 方案适合快速体验项目功能,无需在本机手动安装 PHP、
## 架构说明 ## 架构说明
```text ```text
浏览器 → localhost:8000 → [Nginx 容器:80] → [PHP-FPM 容器:9000] → Laravel 应用 浏览器 → admin.laravel13.elevue → [Nginx 容器:80] → [PHP-FPM 容器:9000] → Laravel 应用
↕ ↕
[MySQL 容器:3306] + [Redis 容器:6379] [MySQL 容器:3306] + [Redis 容器:6379]
``` ```
@@ -151,7 +151,7 @@ services:
- JWT_SECRET=${JWT_SECRET:-your_random_jwt_secret_key_at_least_32_bytes_long} - JWT_SECRET=${JWT_SECRET:-your_random_jwt_secret_key_at_least_32_bytes_long}
# 文件访问域名(接口返回上传文件的完整地址) # 文件访问域名(接口返回上传文件的完整地址)
- FILE_DOMAIN_URL=${DOMAIN_URL:-http://localhost:8000} - FILE_DOMAIN_URL=${DOMAIN_URL:-http://admin.laravel13.elevue}
# 跨域来源(快速体验默认放开) # 跨域来源(快速体验默认放开)
- CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*} - CORS_ALLOW_ORIGIN=${CORS_ALLOW_ORIGIN:-*}
@@ -435,8 +435,8 @@ REDIS_PASSWORD=Your_Redis_Password_2024
# 应用 # 应用
JWT_SECRET=<32 字符以上随机值,openssl rand -base64 32 生成> JWT_SECRET=<32 字符以上随机值,openssl rand -base64 32 生成>
APP_DEBUG=false APP_DEBUG=false
DOMAIN_URL=http://localhost:8000 DOMAIN_URL=http://admin.laravel13.elevue
CORS_ALLOW_ORIGIN=http://localhost:8000 CORS_ALLOW_ORIGIN=http://admin.laravel13.elevue
``` ```
:::tip 与本地 .env 的关系 :::tip 与本地 .env 的关系
@@ -460,8 +460,8 @@ CORS_ALLOW_ORIGIN=http://localhost:8000
| 服务 | 地址 | 说明 | | 服务 | 地址 | 说明 |
|------|------|------| |------|------|------|
| 后端 API | http://localhost:8000 | Nginx → PHP-FPM → Laravel | | 后端 API | http://admin.laravel13.elevue | Nginx → PHP-FPM → Laravel |
| 健康检查 | http://localhost:8000/up | Laravel 内置健康路由 | | 健康检查 | http://admin.laravel13.elevue/up | Laravel 内置健康路由 |
| MySQL | localhost:3306 | 用户名 root,默认密码 root123 | | MySQL | localhost:3306 | 用户名 root,默认密码 root123 |
| Redis | localhost:6379 | 默认密码 redis123 | | Redis | localhost:6379 | 默认密码 redis123 |
@@ -469,15 +469,15 @@ CORS_ALLOW_ORIGIN=http://localhost:8000
```bash ```bash
# 健康检查 # 健康检查
curl http://localhost:8000/up curl http://admin.laravel13.elevue/up
# 验证码(同时验证 PHP→Redis 链路,返回 code=0 即成功) # 验证码(同时验证 PHP→Redis 链路,返回 code=0 即成功)
curl http://localhost:8000/api/captcha curl http://admin.laravel13.elevue/api/captcha
# → {"code":0,"ok":true,"msg":"操作成功","data":{...}} # → {"code":0,"ok":true,"msg":"操作成功","data":{...}}
# 登录获取 Token(默认管理员账号 admin / 123456,登录后立即修改) # 登录获取 Token(默认管理员账号 admin / 123456,登录后立即修改)
# 验证码参数 key/code 与 /api/captcha 响应字段一致 # 验证码参数 key/code 与 /api/captcha 响应字段一致
curl -X POST http://localhost:8000/api/login \ curl -X POST http://admin.laravel13.elevue/api/login \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456","key":"<captcha_key>","code":"<验证码字符>"}' -d '{"username":"admin","password":"123456","key":"<captcha_key>","code":"<验证码字符>"}'
``` ```
@@ -88,7 +88,7 @@ pnpm install # 重新安装所有依赖
A: 检查以下几点: A: 检查以下几点:
1. 后端服务是否启动(`curl http://localhost:8000/api/captcha` 验证) 1. 后端服务是否启动(`curl http://admin.laravel13.elevue/api/captcha` 验证)
2. Vite 代理配置是否正确(`vite.config.ts` 中的 `target`,改完重启 `pnpm dev`) 2. Vite 代理配置是否正确(`vite.config.ts` 中的 `target`,改完重启 `pnpm dev`)
3. API 路径是否正确(应以 `/api` 开头) 3. API 路径是否正确(应以 `/api` 开头)
@@ -16,7 +16,7 @@
```bash ```bash
# GET 请求获取验证码(返回 Base64 编码的图片 + 验证码 key) # GET 请求获取验证码(返回 Base64 编码的图片 + 验证码 key)
curl http://localhost:8000/api/captcha curl http://admin.laravel13.elevue/api/captcha
``` ```
响应示例: 响应示例:
@@ -52,7 +52,7 @@ curl http://localhost:8000/api/captcha
# password → 密码 # password → 密码
# key → 步骤一返回的验证码 key(与 captcha 响应字段命名一致) # key → 步骤一返回的验证码 key(与 captcha 响应字段命名一致)
# code → 验证码图片上显示的字符 # code → 验证码图片上显示的字符
curl -X POST http://localhost:8000/api/login \ curl -X POST http://admin.laravel13.elevue/api/login \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"username": "admin", "username": "admin",
@@ -100,7 +100,7 @@ curl -X POST http://localhost:8000/api/login \
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# pageNo=1\&pageSize=10 → 分页参数(\& 是 bash 转义,防止 & 被解释为后台运行符) # pageNo=1\&pageSize=10 → 分页参数(\& 是 bash 转义,防止 & 被解释为后台运行符)
# -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token # -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token
curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \ curl "http://admin.laravel13.elevue/api/user/page?pageNo=1&pageSize=10" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
``` ```
@@ -136,33 +136,33 @@ curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 获取当前登录用户信息 # 获取当前登录用户信息
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/index/getUser \ curl http://admin.laravel13.elevue/api/index/getUser \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 获取当前用户的菜单列表(用于前端动态路由) # 获取当前用户的菜单列表(用于前端动态路由)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/index/getMenus \ curl http://admin.laravel13.elevue/api/index/getMenus \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 获取字典项(gender = 性别字典的编码) # 获取字典项(gender = 性别字典的编码)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl http://localhost:8000/api/dict/item/getDictItemList/gender \ curl http://admin.laravel13.elevue/api/dict/item/getDictItemList/gender \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 刷新 Token(access_token 过期后,用 refresh_token 换取新的令牌对) # 刷新 Token(access_token 过期后,用 refresh_token 换取新的令牌对)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# refresh 为公开接口(无需 access_token) # refresh 为公开接口(无需 access_token)
curl -X POST http://localhost:8000/api/refresh \ curl -X POST http://admin.laravel13.elevue/api/refresh \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"refresh_token": "<your_refresh_token>"}' -d '{"refresh_token": "<your_refresh_token>"}'
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 登出(令牌即时失效,需携带 access_token) # 登出(令牌即时失效,需携带 access_token)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curl -X POST http://localhost:8000/api/logout \ curl -X POST http://admin.laravel13.elevue/api/logout \
-H "Authorization: Bearer <your_token>" -H "Authorization: Bearer <your_token>"
``` ```
@@ -224,7 +224,7 @@ HTTP 状态码为 200,前端据 body `code=401` 触发跳转登录。
## ApiFox 配置建议 ## ApiFox 配置建议
1. 创建环境,设置变量: 1. 创建环境,设置变量:
- `base_url` = `http://localhost:8000` - `base_url` = `http://admin.laravel13.elevue`
- `token` = 登录后填入 access_token - `token` = 登录后填入 access_token
2. 在请求头中使用变量: 2. 在请求头中使用变量:
@@ -40,57 +40,79 @@ npm config set registry https://registry.npmmirror.com
### `.env.development`(开发环境) ### `.env.development`(开发环境)
摘自前端项目 `ui/.env.development`:
```bash ```bash
# 服务端口 # 只在开发模式中被载入
VITE_PORT = 8001 VITE_PORT = 8001
# 公共基础路径 # 网站根目录
VITE_PUBLIC_PATH = / VITE_PUBLIC_PATH = /
# 代理配置(JSON 数组格式:[["前缀","目标地址"]]) # 是否开启mock
VITE_PROXY = [["/api","http://localhost:8000/api"]] VITE_USE_MOCK = false
# API 接口基础路径(留空,由代理处理) # 是否删除console
VITE_DROP_CONSOLE = true
# 跨域代理,可以配置多个,请注意不要换行
VITE_PROXY=[["/api","http://admin.laravel13.elevue/api"]]
# API 接口地址
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
# API 前缀 # 图片上传地址
VITE_GLOB_UPLOAD_URL=
# 图片前缀地址
VITE_GLOB_IMG_URL=
# 是否启用gzip压缩或brotli压缩
# 可选: gzip | brotli | none
# 如果你需要多种形式,你可以用','来分隔
VITE_BUILD_COMPRESS = 'none'
# 使用压缩时是否删除原始文件,默认为false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api
# 上传接口地址
VITE_GLOB_UPLOAD_URL = /api/upload
# 图片访问地址(附件域名,对应后端 FILE_DOMAIN_URL)
VITE_GLOB_IMG_URL = http://file.laravel13.elevue
# 是否启用 Mock 数据(false = 使用真实后端 API)
VITE_USE_MOCK = false
``` ```
:::tip 代理目标
`VITE_PROXY` 默认指向 Laragon 虚拟主机 `http://admin.laravel13.elevue/api`(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html));直连 `php artisan serve` 时改为 `[["/api","http://admin.laravel13.elevue/api"]]`。修改后需重启 `pnpm dev` 生效。
:::
### `.env.production`(生产环境) ### `.env.production`(生产环境)
摘自前端项目 `ui/.env.production`:
```bash ```bash
# 公共基础路径 # 是否开启mock
VITE_USE_MOCK = false
# 网站根目录
VITE_PUBLIC_PATH = / VITE_PUBLIC_PATH = /
# 是否在生产环境删除 console # 是否删除console
VITE_DROP_CONSOLE = true VITE_DROP_CONSOLE = true
# API 接口基础路径(留空,由 Nginx 代理处理) # API
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
# API 前缀 # 图片上传地址
VITE_GLOB_UPLOAD_URL=
# 图片前缀地址
VITE_GLOB_IMG_URL=
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api
# 上传接口地址 # 是否启用gzip压缩或brotli压缩
VITE_GLOB_UPLOAD_URL = /api/upload # 可选: gzip | brotli | none
# 如果你需要多种形式,你可以用','来分隔
VITE_BUILD_COMPRESS = 'none'
# 图片访问地址 # 使用压缩时是否删除原始文件,默认为false
VITE_GLOB_IMG_URL = https://file.example.com
# 是否启用 gzip 压缩
VITE_BUILD_COMPRESS = gzip
# 压缩后是否删除原文件
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
``` ```
@@ -117,31 +139,33 @@ npm run dev
## 步骤四:配置 API 代理 ## 步骤四:配置 API 代理
开发环境下,前端通过 Vite 代理将 `/api` 请求转发到后端服务。 开发环境下,前端通过 Vite 代理将 `/api` 请求转发到后端服务,代理规则由 `.env.development` 的 `VITE_PROXY` 驱动(并非写死在 `vite.config.ts` 中)。
在 `vite.config.ts` 中配置代理: `vite.config.ts` 读取 `VITE_PROXY` 并调用 `createProxy` 生成代理:
```typescript ```typescript
// ui/vite.config.ts // ui/vite.config.ts
server: { server: {
port: 8001, // 开发服务器端口(对应 VITE_PORT) host: true,
proxy: { port: VITE_PORT, // 开发服务器端口(对应 VITE_PORT)
'/api': { proxy: createProxy(VITE_PROXY), // 由 .env.development 的 VITE_PROXY 生成
target: 'http://localhost:8000/api', // 后端服务地址(对应 VITE_PROXY)
changeOrigin: true, // 开启跨域(修改请求头的 Origin)
rewrite: (path) => path, // 保持路径不变(不做路径替换)
},
},
} }
``` ```
后端使用虚拟域名时(见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html)),target 改为对应地址: `createProxy`(`build/vite/proxy.ts`)为每个 `[前缀, 目标地址]` 生成一条代理规则:
```typescript ```typescript
target: 'http://admin.laravel13.elevue/api', ret[prefix] = {
target, // 目标地址(自带 /api,如 http://admin.laravel13.elevue/api)
changeOrigin: true, // 修改请求头 Origin,解决跨域
ws: true, // 支持 WebSocket
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), // 去掉 /api 前缀,由 target 中的 /api 补回
};
``` ```
确保后端服务已启动(`php artisan serve`),然后访问 `http://localhost:8001`。 更换后端地址时修改 `.env.development` 中的 `VITE_PROXY` 即可(直连 `php artisan serve` 时改为 `http://admin.laravel13.elevue/api`,见 [2.2 Laragon 环境搭建](/zh/quickstart/laragon.html)),改后重启 `pnpm dev` 生效。
确保后端服务已启动(Laragon 或 `php artisan serve`),然后访问 `http://localhost:8001`。
## 步骤五:验证前后端联调 ## 步骤五:验证前后端联调
@@ -151,7 +175,7 @@ target: 'http://admin.laravel13.elevue/api',
4. 登录成功后进入控制台页面 4. 登录成功后进入控制台页面
:::tip 代理原理 :::tip 代理原理
浏览器 → `http://localhost:8001/api/login` → Vite 代理 → `http://localhost:8000/api/login` → 后端处理 浏览器 → `http://localhost:8001/api/login` → Vite 代理(rewrite 去掉 `/api`、target 补回)→ `http://admin.laravel13.elevue/api/login` → 后端处理
代理方式下前端无需关心跨域;若改为直连后端(`VITE_GLOB_API_URL` 配置完整地址),则依赖后端 CORS 配置(`.env` 中 `CORS_ALLOW_ORIGIN`)。 代理方式下前端无需关心跨域;若改为直连后端(`VITE_GLOB_API_URL` 配置完整地址),则依赖后端 CORS 配置(`.env` 中 `CORS_ALLOW_ORIGIN`)。
::: :::
@@ -205,12 +229,12 @@ server: {
检查后端服务是否启动,以及代理配置是否正确: 检查后端服务是否启动,以及代理配置是否正确:
```bash ```bash
# 确认后端在运行(直接请求后端验证码接口) # 确认后端在运行(直接请求后端验证码接口,默认虚拟主机)
curl http://localhost:8000/api/captcha curl http://admin.laravel13.elevue/api/captcha
# → {"code":0,...} 即后端正常 # → {"code":0,...} 即后端正常
``` ```
再检查 `vite.config.ts` 中 `proxy.target` 是否指向后端实际地址(改完需重启 `pnpm dev`)。 再检查 `.env.development` 中 `VITE_PROXY` 的目标地址是否指向后端实际地址(改完需重启 `pnpm dev`)。
### 4. 登录报 401 ### 4. 登录报 401
@@ -87,7 +87,7 @@ export const sharedConfig = defineConfig({
socialLinks: [ socialLinks: [
{ {
icon: "github", icon: "github",
link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue",
}, },
], ],
@@ -98,7 +98,7 @@ export const sharedConfig = defineConfig({
}, },
editLink: { editLink: {
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue", pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue",
text: "对本页提出修改建议", text: "对本页提出修改建议",
}, },
@@ -1,9 +1,9 @@
import type { DefaultTheme, LocaleSpecificConfig } from "vitepress"; import type { DefaultTheme, LocaleSpecificConfig } from "vitepress";
export const META_URL = "https://www.rxthink.cn"; export const META_URL = "https://www.rxthink.cn";
export const META_TITLE = "RXThinkCMF Laravel13+ElementPlus 开发文档"; export const META_TITLE = "RXThinkCMF Laravel13+NaiveUI 开发文档";
export const META_DESCRIPTION = export const META_DESCRIPTION =
"RXThinkCMF 企业级前后端分离后台管理系统 - Laravel 13 + Vue3 + ElementPlus"; "RXThinkCMF 企业级前后端分离后台管理系统 - Laravel 13 + Vue3 + NaiveUI";
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = { export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
description: META_DESCRIPTION, description: META_DESCRIPTION,
@@ -20,7 +20,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
var _hmt = _hmt || []; var _hmt = _hmt || [];
(function() { (function() {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?4f06f01017cdd5c1a95a58a6787f4d10"; hm.src = "https://hm.baidu.com/hm.js?8c4ed7e2f1d12bafe9e22e93e30237d4";
var s = document.getElementsByTagName("script")[0]; var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
@@ -30,7 +30,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
themeConfig: { themeConfig: {
editLink: { editLink: {
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue", pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue",
text: "对本页提出修改建议", text: "对本页提出修改建议",
}, },
@@ -160,10 +160,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
text: "相关链接", text: "相关链接",
items: [ items: [
{ text: "官方网站", link: "https://www.rxthink.cn" }, { text: "官方网站", link: "https://www.rxthink.cn" },
{ text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue" }, { text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue" },
{ text: "Laravel 官网", link: "https://laravel.com/" }, { text: "Laravel 官网", link: "https://laravel.com/" },
{ text: "Vue3 文档", link: "https://cn.vuejs.org/" }, { text: "Vue3 文档", link: "https://cn.vuejs.org/" },
{ text: "ElementPlus", link: "https://element-plus.org/zh-CN/" }, { text: "NaiveUI", link: "https://www.naiveui.com/" },
], ],
}, },
], ],
@@ -503,7 +503,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: "L. 贡献指南", link: "/zh/other/contributing.html" }, { text: "L. 贡献指南", link: "/zh/other/contributing.html" },
{ text: "M. 免责声明", link: "/zh/other/declare.html" }, { text: "M. 免责声明", link: "/zh/other/declare.html" },
{ text: "N. 广告赞助", link: "/zh/other/sponsor.html" }, { text: "N. 广告赞助", link: "/zh/other/sponsor.html" },
{ text: "O. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_EleVue" }, { text: "O. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_Laravel13_NaiveVue" },
], ],
}, },
], ],

Some files were not shown because too many files have changed in this diff Show More