签入版本
@@ -13,6 +13,19 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?4f06f01017cdd5c1a95a58a6787f4d10";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?4f06f01017cdd5c1a95a58a6787f4d10";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?4f06f01017cdd5c1a95a58a6787f4d10";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
|||||||
@@ -13,11 +13,24 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?8c4cab94b9b80db6f92e6dbb16b41762";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: "https://gitee.com/rxthink/thinkphp6",
|
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_AntdVue",
|
||||||
text: "对本页提出修改建议",
|
text: "对本页提出修改建议",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## What this repository is
|
|
||||||
|
|
||||||
A VitePress 1.x documentation site (Chinese, `zh-CN`) for the RXThinkCMF ThinkPHP6 + AntDesign admin framework. There is no application source code here — only Markdown content, VitePress config, and a custom theme. The documented framework's PHP/Vue source lives in separate repositories.
|
|
||||||
|
|
||||||
Note: much of the body content is shared text across the RXThinkCMF variant docs; some pages (e.g. `zh/introduction.md`, `zh/tech-stack.md`, config metadata in `.vitepress/config/zh.ts`) still say "ElementPlus" while the homepage says "AntDesign". Match the surrounding page's terminology rather than assuming.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm install # install dependencies (README uses pnpm; a yarn.lock also exists)
|
|
||||||
pnpm docs # dev server (vitepress dev .)
|
|
||||||
pnpm docs:build # static build — ALSO the link checker (build fails on dead internal links)
|
|
||||||
pnpm docs:preview # preview the built site
|
|
||||||
```
|
|
||||||
|
|
||||||
There is no lint or test tooling. `pnpm docs:build` is the verification step: it fails on broken internal links (only `localhost` links are exempt via `ignoreDeadLinks` in `.vitepress/config/index.ts`). Run it after adding, renaming, or removing pages.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
- **`zh/`** — all documentation content (~140 pages), organized by chapter: `quickstart/`, `guide/`, `architecture/`, `core/`, `modules/`, `tools/`, `api-spec/`, `frontend/`, `generator/`, `deploy/`, `standards/`, `appendix/`, `other/`.
|
|
||||||
- **`index.md`** — homepage (hero + features frontmatter).
|
|
||||||
- **`.vitepress/config/`**
|
|
||||||
- `shared.ts` — global config: light-only appearance, custom `slugify` for anchors, markdown themes, head tags, local search.
|
|
||||||
- `zh.ts` — **nav and the entire sidebar tree**. The sidebar is fully manual: every page must be added here or it will not appear in navigation.
|
|
||||||
- `index.ts` — locale wiring (root locale is Chinese; the English locale in `en.ts` is commented out/inactive).
|
|
||||||
- **`.vitepress/theme/`** — extends the default VitePress theme: sponsors components (`AsideSponsors`, `HomeSponsors`), global component registrations (`VideoModuleLink`, `vImageViewer`), image-viewer plugin, custom styles.
|
|
||||||
- **`public/`** — static assets referenced by absolute path (`/logo.png`, `/demo/*.png`, `/banners/`, `/volume1|3|5/`, sponsor images).
|
|
||||||
|
|
||||||
## Content conventions
|
|
||||||
|
|
||||||
- **All content is written in Simplified Chinese.**
|
|
||||||
- Each page's H1 includes its chapter number matching the sidebar label (e.g. `# 12.3 Git 提交规范`), usually followed by a `:::tip 概述` container.
|
|
||||||
- Internal links use absolute paths with an explicit `.html` suffix: `[开发环境准备](/zh/quickstart/environment.html)` — this applies both in Markdown prose and in `zh.ts` sidebar entries.
|
|
||||||
- Images are stored under `public/` and embedded with absolute paths, typically as raw HTML `<img src="/demo/1.png"/>`.
|
|
||||||
- Markdown attribute delimiter is `%{ ... }%` (not the default `{ ... }`), configured in `shared.ts`.
|
|
||||||
- When adding a page: create the file under `zh/`, add the sidebar entry in `.vitepress/config/zh.ts` with its number, then run `pnpm docs:build` to verify links.
|
|
||||||
|
|
||||||
## Git
|
|
||||||
|
|
||||||
- The git repository root is the **parent directory** (`.../v3/document`), which contains 9 sibling variant doc sites (ThinkPhp6/ThinkPhp8/Laravel13 × EleVue/AntdVue/NaiveVue). `git status` will show changes outside this directory — only touch files under this project's folder unless asked otherwise.
|
|
||||||
- Commit messages follow Conventional Commits as documented in `zh/standards/git.md` (e.g. `docs: 补充多数据库部署文档`), though historical commits here mostly read `签入版本`.
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
allowBuilds:
|
|
||||||
'@parcel/watcher': set this to true or false
|
|
||||||
esbuild: set this to true or false
|
|
||||||
vue-demi: set this to true or false
|
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
| Region | `components/Region/` | 行政区划 | `value`、`level` |
|
| Region | `components/Region/` | 行政区划 | `value`、`level` |
|
||||||
| Cropper | `components/Cropper/` | 头像裁剪 | `image`、`aspectRatio` |
|
| Cropper | `components/Cropper/` | 头像裁剪 | `image`、`aspectRatio` |
|
||||||
| NumberInput | `components/numberInput/` | 数字输入 | `value`、`min`、`max` |
|
| NumberInput | `components/numberInput/` | 数字输入 | `value`、`min`、`max` |
|
||||||
| PriceInput | `components/priceInput/` | 价格输入 | `value`、`precision` |
|
|
||||||
|
|
||||||
## Excel
|
## Excel
|
||||||
|
|
||||||
@@ -41,7 +40,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 组件 |
|
||||||
|
|
||||||
@@ -51,20 +49,34 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicTable @register="registerTable" />
|
<BasicTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="tableRef"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicTable, useTable } from '@/components/Table';
|
import { ref } from 'vue';
|
||||||
|
import { getExamplePage } from '@/api/tool/example';
|
||||||
|
|
||||||
const [registerTable] = useTable({
|
const tableRef = ref();
|
||||||
api: getExamplePage,
|
const columns = [
|
||||||
columns: [
|
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||||
{ title: 'ID', field: 'id', width: 80 },
|
{ title: '名称', dataIndex: 'name' },
|
||||||
{ title: '名称', field: 'name' },
|
{ title: '状态', dataIndex: 'statusText', width: 100 },
|
||||||
{ title: '状态', field: 'statusText', width: 100 },
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 180 },
|
||||||
],
|
];
|
||||||
});
|
|
||||||
|
const loadDataTable = async (params) => await getExamplePage(params);
|
||||||
|
const handleEdit = (record) => {};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,8 +92,13 @@ import { BasicForm, useForm } from '@/components/Form';
|
|||||||
|
|
||||||
const [registerForm] = useForm({
|
const [registerForm] = useForm({
|
||||||
schemas: [
|
schemas: [
|
||||||
{ field: 'name', label: '名称', component: 'Input', required: true },
|
{
|
||||||
{ field: 'status', label: '状态', component: 'Select', componentProps: { options: [...] } },
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
component: 'Input',
|
||||||
|
rules: [{ required: true, message: '请输入名称', trigger: ['blur'] }],
|
||||||
|
},
|
||||||
|
{ name: 'status', label: '状态', component: 'Select', componentProps: { options: [...] } },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -91,15 +108,19 @@ const [registerForm] = useForm({
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicModal @register="registerModal" @ok="handleSubmit" />
|
<basicModal @register="registerModal" @ok="handleSubmit">
|
||||||
|
<template #default>
|
||||||
|
<!-- 表单内容 -->
|
||||||
|
</template>
|
||||||
|
</basicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicModal, useModal } from '@/components/Modal';
|
import { basicModal, useModal } from '@/components/Modal';
|
||||||
|
|
||||||
const [registerModal, { openModal, closeModal }] = useModal();
|
const [registerModal, { openModal, closeModal }] = useModal();
|
||||||
|
|
||||||
const handleAdd = () => openModal(true, { isUpdate: false });
|
const handleAdd = () => openModal();
|
||||||
const handleSubmit = async () => { /* ... */ closeModal(); };
|
const handleSubmit = async () => { /* ... */ closeModal(); };
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
@@ -74,33 +74,13 @@ await userStore.logout();
|
|||||||
|
|
||||||
| 字段 | 说明 |
|
| 字段 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `navMode` | 导航模式(侧边/顶部) |
|
| `layout` | 布局模式(side 侧边 / top 顶部 / mix 拆分) |
|
||||||
| `navTheme` | 导航主题(暗色/亮色) |
|
| `navTheme` | 导航主题(light / dark) |
|
||||||
| `isShowMenuBar` | 是否显示菜单栏 |
|
| `headerTheme` | 标题栏主题 |
|
||||||
| `isCollapse` | 菜单是否折叠 |
|
| `menuSetting` | 菜单配置(宽度、折叠、固定等) |
|
||||||
|
| `multiTabsSetting` | 多标签页配置 |
|
||||||
---
|
| `permissionMode` | 菜单权限模式(ROLE / BACK) |
|
||||||
|
| `isWaterMark` | 是否显示水印 |
|
||||||
## ossConfigStore
|
|
||||||
|
|
||||||
**文件**:`store/modules/ossConfig.ts`
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `ossConfig` | 云存储配置 |
|
|
||||||
|
|
||||||
路由守卫中初始化,用于文件上传。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## designSettingStore
|
|
||||||
|
|
||||||
**文件**:`store/modules/designSetting.ts`
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `appTheme` | 主题色 |
|
|
||||||
| `darkMode` | 是否暗色模式 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -114,13 +94,29 @@ await userStore.logout();
|
|||||||
|
|
||||||
| Action | 说明 |
|
| Action | 说明 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| `addTab(route)` | 添加标签 |
|
| `addTabs(route)` | 添加标签 |
|
||||||
| `removeTab(route)` | 关闭标签 |
|
| `closeCurrentTab(route)` | 关闭当前标签 |
|
||||||
| `closeOtherTabs(route)` | 关闭其他标签 |
|
| `closeOtherTabs(route, activeKey)` | 关闭其他标签 |
|
||||||
| `closeAllTabs()` | 关闭所有标签 |
|
| `closeAllTabs()` | 关闭所有标签 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## lockscreenStore
|
||||||
|
|
||||||
|
**文件**:`store/modules/lockscreen.ts`
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `isLock` | 是否锁屏 |
|
||||||
|
| `lockTime` | 锁屏剩余时间 |
|
||||||
|
|
||||||
|
| Action | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `setLock(flag)` | 设置锁屏状态(持久化到 Storage) |
|
||||||
|
| `setLockTime(time)` | 设置锁屏时间 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 在组件外使用
|
## 在组件外使用
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
@@ -230,8 +230,6 @@ src/utils/http/axios/
|
|||||||
| 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 核心流程:**
|
||||||
@@ -330,9 +328,10 @@ src/utils/http/axios/
|
|||||||
|
|
||||||
| Hook | 文件 | 说明 |
|
| Hook | 文件 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `usePermission` | `hooks/web/usePermission.ts` | 权限判断(hasPermission、hasSomePermission) |
|
| `usePermission` | `hooks/web/usePermission.ts` | 权限判断(hasPermission、hasEveryPermission、hasSomePermission) |
|
||||||
| `useTable` | `hooks/web/useTable.ts` | 表格通用逻辑(分页、搜索、刷新) |
|
| `usePage` | `hooks/web/usePage.ts` | 分页参数管理 |
|
||||||
| `useForm` | `hooks/web/useForm.ts` | 表单通用逻辑(提交、重置、校验) |
|
| `useECharts` | `hooks/web/useECharts.ts` | ECharts 图表挂载与销毁 |
|
||||||
|
| `useForm` | `components/Form/src/hooks/useForm.ts` | 表单注册(BasicForm 配合 `@register` 使用) |
|
||||||
|
|
||||||
### Directives(自定义指令)
|
### Directives(自定义指令)
|
||||||
|
|
||||||
@@ -347,7 +346,7 @@ src/utils/http/axios/
|
|||||||
|------|------|------|
|
|------|------|------|
|
||||||
| HTTP | `utils/http/axios/` | Axios 封装 |
|
| HTTP | `utils/http/axios/` | Axios 封装 |
|
||||||
| Storage | `utils/Storage.ts` | localStorage 封装 |
|
| Storage | `utils/Storage.ts` | localStorage 封装 |
|
||||||
| Dict | `utils/dict.ts` | 字典工具(从 Store 获取字典值) |
|
| useLockFn | `utils/useLockFn.ts` | 防重复提交(请求级加锁) |
|
||||||
|
|
||||||
## 数据流向
|
## 数据流向
|
||||||
|
|
||||||
|
|||||||
@@ -242,8 +242,12 @@ const [genderOptions, statusOptions] = await Promise.all([
|
|||||||
|
|
||||||
### 封装为通用 Hook
|
### 封装为通用 Hook
|
||||||
|
|
||||||
|
:::warning 说明
|
||||||
|
`ui/src/hooks/web/useDict.ts` 当前项目中尚不存在,以下为建议的封装写法,可按需自行创建。
|
||||||
|
:::
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ui/src/hooks/web/useDict.ts
|
// ui/src/hooks/web/useDict.ts(自行创建)
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { getDictItemList } from '@/api/common/index';
|
import { getDictItemList } from '@/api/common/index';
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
thinkphp6/
|
|
||||||
├── docker/
|
├── docker/
|
||||||
│ ├── Dockerfile # PHP-FPM 应用镜像定义
|
│ ├── Dockerfile # PHP-FPM 应用镜像定义
|
||||||
│ ├── nginx.conf # Nginx 站点配置
|
│ ├── nginx.conf # Nginx 站点配置
|
||||||
|
|||||||
@@ -240,9 +240,7 @@ ui/src/
|
|||||||
│ ├── asyncRoute.ts // 动态路由 Store(菜单/keep-alive)
|
│ ├── asyncRoute.ts // 动态路由 Store(菜单/keep-alive)
|
||||||
│ ├── projectSetting.ts // 项目配置 Store
|
│ ├── projectSetting.ts // 项目配置 Store
|
||||||
│ ├── tabsView.ts // 标签页 Store
|
│ ├── tabsView.ts // 标签页 Store
|
||||||
│ ├── designSetting.ts // 设计/主题配置 Store
|
│ └── lockscreen.ts // 锁屏 Store
|
||||||
│ ├── lockscreen.ts // 锁屏 Store
|
|
||||||
│ └── ossConfig.ts // 云存储配置 Store
|
|
||||||
├── router/ // 路由配置
|
├── router/ // 路由配置
|
||||||
│ ├── index.ts // 路由实例
|
│ ├── index.ts // 路由实例
|
||||||
│ ├── base.ts // 静态路由(登录/404)
|
│ ├── base.ts // 静态路由(登录/404)
|
||||||
|
|||||||
@@ -8,28 +8,46 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicTable @register="registerTable" />
|
<BasicTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="tableRef"
|
||||||
|
scroll-x="1200"
|
||||||
|
>
|
||||||
|
<template #tableTitle>
|
||||||
|
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
|
||||||
|
<a-button type="link" danger @click="handleDelete(record.id)">删除</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicTable, useTable } from '@/components/Table';
|
import { ref } from 'vue';
|
||||||
|
import { getExamplePage, exampleDelete } from '@/api/tool/example';
|
||||||
|
|
||||||
const [registerTable] = useTable({
|
const tableRef = ref();
|
||||||
api: getExamplePage, // 数据接口
|
const columns = [
|
||||||
columns: [ // 列定义
|
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||||
{ title: 'ID', field: 'id', width: 80 },
|
{ title: '名称', dataIndex: 'name' },
|
||||||
{ title: '名称', field: 'name' },
|
{ title: '状态', dataIndex: 'statusText', width: 100 },
|
||||||
{ title: '状态', field: 'statusText', width: 100 },
|
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
|
||||||
{ title: '创建时间', field: 'createTime', width: 180 },
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 200, fixed: 'right' },
|
||||||
],
|
];
|
||||||
actionColumn: { // 操作列
|
|
||||||
width: 200,
|
// BasicTable 数据加载:返回值直接交给表格渲染
|
||||||
actions: [
|
const loadDataTable = async (params) => await getExamplePage(params);
|
||||||
{ label: '编辑', onClick: handleEdit },
|
|
||||||
{ label: '删除', onClick: handleDelete, type: 'danger' },
|
const handleAdd = () => {};
|
||||||
],
|
const handleEdit = (record) => {};
|
||||||
},
|
const handleDelete = async (id) => {};
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
@@ -42,17 +42,21 @@ const [registerForm] = useForm({
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicModal @register="registerModal" @ok="handleSubmit" />
|
<basicModal @register="registerModal" @ok="handleSubmit">
|
||||||
|
<template #default>
|
||||||
|
<!-- 表单内容 -->
|
||||||
|
</template>
|
||||||
|
</basicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicModal, useModal } from '@/components/Modal';
|
import { basicModal, useModal } from '@/components/Modal';
|
||||||
|
|
||||||
const [registerModal, { openModal, closeModal }] = useModal();
|
const [registerModal, { openModal, closeModal }] = useModal();
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
openModal(true, { isUpdate: false });
|
openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 提交
|
// 提交
|
||||||
@@ -68,12 +72,11 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicUpload v-model="formData.avatar" :action="uploadUrl" />
|
<UploadImg v-model:image-url="formData.avatar" name="example" :fileSize="20" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicUpload } from '@/components/Upload';
|
import UploadImg from '@/components/Upload/Image.vue';
|
||||||
const uploadUrl = '/api/upload/uploadFile';
|
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -81,11 +84,18 @@ const uploadUrl = '/api/upload/uploadFile';
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<Editor v-model="formData.content" />
|
<Editor ref="editorRef" :height="editorHeight" name="content" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Editor from '@/components/Editor/index.vue';
|
import { ref } from 'vue';
|
||||||
|
import Editor from '@/components/Editor/tinymce.vue';
|
||||||
|
|
||||||
|
const editorRef = ref();
|
||||||
|
const editorHeight = 400;
|
||||||
|
|
||||||
|
// 提交时读取编辑器内容(真实项目回填:editorRef.value.myValue = html)
|
||||||
|
const getContent = () => editorRef.value?.myValue;
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -93,11 +103,13 @@ import Editor from '@/components/Editor/index.vue';
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<Password v-model="formData.password" />
|
<Password :value="formData.password" :repeat="true" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Password } from '@/components/Password';
|
import { Password } from '@/components/Password';
|
||||||
|
|
||||||
|
// 组件内部维护输入值与强度校验,仅通过 value 单向传入初始值(无 v-model)
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -77,10 +77,10 @@ interface MenuItem {
|
|||||||
const tabsViewStore = useTabsViewStore();
|
const tabsViewStore = useTabsViewStore();
|
||||||
|
|
||||||
// 添加标签
|
// 添加标签
|
||||||
tabsViewStore.addTab(route);
|
tabsViewStore.addTabs(route);
|
||||||
|
|
||||||
// 关闭标签
|
// 关闭标签
|
||||||
tabsViewStore.removeTab(route);
|
tabsViewStore.closeCurrentTab(route);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 响应式布局
|
## 响应式布局
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
| 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` | 标签页 |
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ Docker 方案适合快速体验项目功能,无需手动安装 PHP、MySQL 等
|
|||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
thinkphp6/
|
|
||||||
├── docker-compose.yml # 容器编排(项目根目录)
|
├── docker-compose.yml # 容器编排(项目根目录)
|
||||||
├── docker/
|
├── docker/
|
||||||
│ ├── Dockerfile # PHP-FPM 镜像定义
|
│ ├── Dockerfile # PHP-FPM 镜像定义
|
||||||
|
|||||||
@@ -13,11 +13,24 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?ad62537a0ef583d006c0a31a129f531d";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: "https://gitee.com/rxthink/thinkphp6",
|
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
||||||
text: "对本页提出修改建议",
|
text: "对本页提出修改建议",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
//
|
//
|
||||||
// themeConfig: {
|
// themeConfig: {
|
||||||
// editLink: {
|
// editLink: {
|
||||||
// pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
// pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue",
|
||||||
// text: "Suggest changes to this page",
|
// text: "Suggest changes to this page",
|
||||||
// },
|
// },
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const sharedConfig = defineConfig({
|
|||||||
socialLinks: [
|
socialLinks: [
|
||||||
{
|
{
|
||||||
icon: "github",
|
icon: "github",
|
||||||
link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ export const sharedConfig = defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_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 ThinkPHP6+ElementPlus 开发文档";
|
export const META_TITLE = "RXThinkCMF ThinkPHP6+NaiveUI 开发文档";
|
||||||
export const META_DESCRIPTION =
|
export const META_DESCRIPTION =
|
||||||
"RXThinkCMF 企业级前后端分离后台管理系统 - ThinkPHP6.1 + Vue3 + ElementPlus";
|
"RXThinkCMF 企业级前后端分离后台管理系统 - ThinkPHP6.1 + Vue3 + NaiveUI";
|
||||||
|
|
||||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||||
description: META_DESCRIPTION,
|
description: META_DESCRIPTION,
|
||||||
@@ -13,11 +13,24 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?366a9046392d7b07fe770e30b68bca1f";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: "https://gitee.com/rxthink/thinkphp6",
|
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue",
|
||||||
text: "对本页提出修改建议",
|
text: "对本页提出修改建议",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -147,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_ThinkPhp6_EleVue" },
|
{ text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue" },
|
||||||
{ text: "ThinkPHP 官网", link: "https://www.thinkphp.cn/" },
|
{ text: "ThinkPHP 官网", link: "https://www.thinkphp.cn/" },
|
||||||
{ 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/" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -489,7 +502,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
{ text: "K. 贡献指南", link: "/zh/other/contributing.html" },
|
{ text: "K. 贡献指南", link: "/zh/other/contributing.html" },
|
||||||
{ text: "L. 免责声明", link: "/zh/other/declare.html" },
|
{ text: "L. 免责声明", link: "/zh/other/declare.html" },
|
||||||
{ text: "M. 广告赞助", link: "/zh/other/sponsor.html" },
|
{ text: "M. 广告赞助", link: "/zh/other/sponsor.html" },
|
||||||
{ text: "N. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue" },
|
{ text: "N. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_NaiveVue" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 852 KiB After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 51 KiB |
@@ -128,14 +128,11 @@ export function getDictItemList(code: string) {
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
<n-select
|
||||||
<el-option
|
v-model:value="formData.status"
|
||||||
v-for="item in statusOptions"
|
:options="statusOptions"
|
||||||
:key="item.value"
|
placeholder="请选择状态"
|
||||||
:label="item.name"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
/>
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -52,7 +52,11 @@
|
|||||||
## 前端错误处理
|
## 前端错误处理
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Axios 响应拦截器
|
// Axios 响应拦截器(src/utils/http/axios/index.ts)
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
|
||||||
|
const $message = useMessage();
|
||||||
|
|
||||||
axios.interceptors.response.use(response => {
|
axios.interceptors.response.use(response => {
|
||||||
const { code, msg, data } = response.data;
|
const { code, msg, data } = response.data;
|
||||||
|
|
||||||
@@ -67,12 +71,12 @@ axios.interceptors.response.use(response => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (code === 403) {
|
if (code === 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));
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -253,6 +253,9 @@ $this->fail('缺少id参数', 1);
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/utils/http/axios/index.ts
|
// src/utils/http/axios/index.ts
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
|
||||||
|
const $message = useMessage();
|
||||||
|
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
response => {
|
response => {
|
||||||
@@ -272,16 +275,16 @@ axios.interceptors.response.use(
|
|||||||
|
|
||||||
// 权限不足
|
// 权限不足
|
||||||
if (code === 403) {
|
if (code === 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);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -301,13 +304,17 @@ const fetchData = async () => {
|
|||||||
### 处理导入结果
|
### 处理导入结果
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
const handleImport = async (file) => {
|
const handleImport = async (file) => {
|
||||||
const res = await importUsers(file);
|
const res = await importUsers(file);
|
||||||
// res = { count: 8, errors: [{ row: 3, reason: "..." }] }
|
// res = { count: 8, errors: [{ row: 3, 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} 条`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -95,51 +95,65 @@ gif, jpg, jpeg, png, bmp, webp, svg
|
|||||||
|
|
||||||
## 前端使用
|
## 前端使用
|
||||||
|
|
||||||
### Element Plus Upload 组件
|
### NaiveUI 上传组件
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<!-- 上传文件(默认 type=file) -->
|
<!-- 上传文件(默认 type=file) -->
|
||||||
<el-upload
|
<n-upload
|
||||||
:action="'/api/upload/uploadFile'"
|
:action="'/api/upload/uploadFile'"
|
||||||
:headers="{ Authorization: `Bearer ${token}` }"
|
:headers="uploadHeaders"
|
||||||
:on-success="handleUploadSuccess"
|
@before-upload="beforeUpload"
|
||||||
:before-upload="beforeUpload"
|
@change="handleUploadChange"
|
||||||
>
|
>
|
||||||
<el-button type="primary">上传文件</el-button>
|
<n-button type="primary">上传文件</n-button>
|
||||||
</el-upload>
|
</n-upload>
|
||||||
|
|
||||||
<!-- 上传图片(指定 type=image) -->
|
<!-- 上传图片(指定 type=image) -->
|
||||||
<el-upload
|
<n-upload
|
||||||
:action="'/api/upload/uploadFile?type=image'"
|
:action="'/api/upload/uploadFile?type=image'"
|
||||||
:headers="{ Authorization: `Bearer ${token}` }"
|
:headers="uploadHeaders"
|
||||||
:on-success="handleImageSuccess"
|
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
|
@change="handleImageChange"
|
||||||
>
|
>
|
||||||
<el-button type="primary">上传图片</el-button>
|
<n-button type="primary">上传图片</n-button>
|
||||||
</el-upload>
|
</n-upload>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
|
const uploadHeaders = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const beforeUpload = (file) => {
|
const beforeUpload = ({ file }) => {
|
||||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
const isLt10M = file.file.size / 1024 / 1024 < 10;
|
||||||
if (!isLt10M) {
|
if (!isLt10M) {
|
||||||
ElMessage.error('文件大小不能超过 10MB');
|
message.error('文件大小不能超过 10MB');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return isLt10M;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadSuccess = (response) => {
|
// 上传结束,解析响应
|
||||||
if (response.code === 0) {
|
const parseResponse = (data) => {
|
||||||
form.value.file = response.data.fileUrl;
|
if (data.code === 0) {
|
||||||
|
return data.data.fileUrl;
|
||||||
|
}
|
||||||
|
message.error(data.msg || '上传失败');
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploadChange = (data) => {
|
||||||
|
if (data.file.status === 'finished') {
|
||||||
|
form.value.file = parseResponse(JSON.parse(data.file.event.target.response));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageSuccess = (response) => {
|
const handleImageChange = (data) => {
|
||||||
if (response.code === 0) {
|
if (data.file.status === 'finished') {
|
||||||
form.value.avatar = response.data.fileUrl;
|
form.value.avatar = parseResponse(JSON.parse(data.file.event.target.response));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -189,25 +203,19 @@ await exampleAdd({
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div class="avatar-upload">
|
<div class="avatar-upload">
|
||||||
<el-avatar :src="form.avatar" :size="80" />
|
<n-avatar :src="form.avatar" :size="80" />
|
||||||
<el-upload
|
<UploadImg
|
||||||
:action="'/api/upload/uploadFile?type=image'"
|
v-model:image-url="form.avatar"
|
||||||
:headers="{ Authorization: `Bearer ${token}` }"
|
:fileType="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']"
|
||||||
:show-file-list="false"
|
:fileSize="10"
|
||||||
:on-success="handleAvatarSuccess"
|
/>
|
||||||
accept="image/*"
|
|
||||||
>
|
|
||||||
<el-button size="small">更换头像</el-button>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const form = ref({ avatar: '' });
|
import { UploadImg } from '@/components/Upload';
|
||||||
|
|
||||||
const handleAvatarSuccess = (response) => {
|
const form = ref({ avatar: '' });
|
||||||
form.value.avatar = response.data.fileUrl;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -234,7 +242,7 @@ BaseLogic::add()
|
|||||||
```ini
|
```ini
|
||||||
; .env
|
; .env
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf ; 上传根目录
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads ; 上传根目录
|
||||||
DOMAIN_URL = https://cdn.example.com ; 文件访问域名
|
DOMAIN_URL = https://cdn.example.com ; 文件访问域名
|
||||||
MAX_SIZE = 10 ; 单文件最大 10MB
|
MAX_SIZE = 10 ; 单文件最大 10MB
|
||||||
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp,svg
|
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp,svg
|
||||||
|
|||||||
@@ -51,20 +51,25 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicTable @register="registerTable" />
|
<BasicTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="basicTableRef"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicTable, useTable } from '@/components/Table';
|
import { BasicTable } from '@/components/Table';
|
||||||
|
import { getExamplePage } from '@/api/tool/example';
|
||||||
|
|
||||||
const [registerTable] = useTable({
|
const columns = [
|
||||||
api: getExamplePage,
|
{ title: 'ID', key: 'id', width: 80 },
|
||||||
columns: [
|
{ title: '名称', key: 'name' },
|
||||||
{ title: 'ID', field: 'id', width: 80 },
|
{ title: '状态', key: 'statusText', width: 100 },
|
||||||
{ title: '名称', field: 'name' },
|
];
|
||||||
{ title: '状态', field: 'statusText', width: 100 },
|
|
||||||
],
|
const loadDataTable = async (params) => await getExamplePage({ ...params });
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,8 +85,8 @@ import { BasicForm, useForm } from '@/components/Form';
|
|||||||
|
|
||||||
const [registerForm] = useForm({
|
const [registerForm] = useForm({
|
||||||
schemas: [
|
schemas: [
|
||||||
{ field: 'name', label: '名称', component: 'Input', required: true },
|
{ field: 'name', label: '名称', component: 'NInput', required: true },
|
||||||
{ field: 'status', label: '状态', component: 'Select', componentProps: { options: [...] } },
|
{ field: 'status', label: '状态', component: 'NSelect', componentProps: { options: [...] } },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本
|
├── mysql/rxthinkcmf.thinkphp6.naivevue.sql # MySQL 版本
|
||||||
├── postgresql/rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
├── postgresql/rxthinkcmf.thinkphp6.naivevue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
├── sqlserver/rxthinkcmf.thinkphp6.naivevue.sql # SQL Server 版本
|
||||||
├── oracle/rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
├── oracle/rxthinkcmf.thinkphp6.naivevue.sql # Oracle 版本
|
||||||
└── sqlite/rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── sqlite/rxthinkcmf.thinkphp6.naivevue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。
|
各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
| TypeScript | - | JavaScript 超集,类型安全 |
|
| TypeScript | - | JavaScript 超集,类型安全 |
|
||||||
| Vite | - | 前端构建工具 |
|
| Vite | - | 前端构建工具 |
|
||||||
| Pinia | - | Vue 3 官方状态管理库 |
|
| Pinia | - | Vue 3 官方状态管理库 |
|
||||||
| ElementPlus | - | Vue 3 UI 组件库 |
|
| NaiveUI | - | Vue 3 UI 组件库 |
|
||||||
| Axios | - | HTTP 请求库 |
|
| Axios | - | HTTP 请求库 |
|
||||||
| TailwindCSS | - | 原子化 CSS 框架 |
|
| TailwindCSS | - | 原子化 CSS 框架 |
|
||||||
| TinyMce | - | 富文本编辑器 |
|
| TinyMce | - | 富文本编辑器 |
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ ALTER TABLE think_city ADD INDEX idx_pid (pid);
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql
|
TYPE = mysql
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = root
|
USERNAME = root
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 3306
|
HOSTPORT = 3306
|
||||||
@@ -304,7 +304,7 @@ HOSTPORT = 3306
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = pgsql
|
TYPE = pgsql
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = postgres
|
USERNAME = postgres
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 5432
|
HOSTPORT = 5432
|
||||||
@@ -314,7 +314,7 @@ HOSTPORT = 5432
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = sqlsrv
|
TYPE = sqlsrv
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = sa
|
USERNAME = sa
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 1433
|
HOSTPORT = 1433
|
||||||
@@ -324,7 +324,7 @@ HOSTPORT = 1433
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = oracle
|
TYPE = oracle
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = root
|
USERNAME = root
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 1521
|
HOSTPORT = 1521
|
||||||
@@ -400,7 +400,7 @@ MySQL 是项目默认推荐的关系型数据库,XAMPP 已自带,开箱即
|
|||||||
# 若前面有分号则去掉,保存后重启 XAMPP
|
# 若前面有分号则去掉,保存后重启 XAMPP
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
D:\xampp8.2\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
D:\xampp8.2\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -409,7 +409,7 @@ brew services start mysql
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -419,7 +419,7 @@ sudo systemctl enable mysql
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -429,7 +429,7 @@ sudo systemctl enable mysqld
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -454,7 +454,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
psql -U postgres -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -465,8 +465,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.naivevue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -477,8 +477,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.naivevue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -490,8 +490,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.naivevue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -558,7 +558,7 @@ SQL Server 是微软商业数据库,XAMPP 已自带 pdo_sqlsrv 驱动,Window
|
|||||||
# extension=php_pdo_sqlsrv_82_ts_x64.dll
|
# extension=php_pdo_sqlsrv_82_ts_x64.dll
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -571,7 +571,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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -593,7 +593,7 @@ echo "extension=pdo_sqlsrv.so" | sudo tee /etc/php/8.2/mods-available/pdo_sqlsrv
|
|||||||
sudo phpenmod pdo_sqlsrv
|
sudo phpenmod pdo_sqlsrv
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -613,7 +613,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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -647,7 +647,7 @@ Oracle 是企业级商业数据库,需手动安装 Instant Client 和 pdo_oci
|
|||||||
# .env 配置
|
# .env 配置
|
||||||
# DATABASE.TYPE = oracle
|
# DATABASE.TYPE = oracle
|
||||||
# DATABASE.HOSTNAME = 127.0.0.1
|
# DATABASE.HOSTNAME = 127.0.0.1
|
||||||
# DATABASE.DATABASE = rxthinkcmf.thinkphp6.elevue
|
# DATABASE.DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
# DATABASE.USERNAME = root
|
# DATABASE.USERNAME = root
|
||||||
# DATABASE.PASSWORD = root
|
# DATABASE.PASSWORD = root
|
||||||
# DATABASE.HOSTPORT = 1521
|
# DATABASE.HOSTPORT = 1521
|
||||||
@@ -679,8 +679,8 @@ sudo phpenmod pdo_oci
|
|||||||
|
|
||||||
# 4. 创建表空间和用户
|
# 4. 创建表空间和用户
|
||||||
sqlplus system/root@localhost:1521/XE <<EOF
|
sqlplus system/root@localhost:1521/XE <<EOF
|
||||||
CREATE TABLESPACE "rxthinkcmf_thinkphp6_elevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_elevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
CREATE TABLESPACE "rxthinkcmf_thinkphp6_naivevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_naivevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
||||||
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_elevue";
|
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_naivevue";
|
||||||
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
||||||
GRANT UNLIMITED TABLESPACE TO "root";
|
GRANT UNLIMITED TABLESPACE TO "root";
|
||||||
EXIT;
|
EXIT;
|
||||||
@@ -703,8 +703,8 @@ sudo phpenmod pdo_oci
|
|||||||
|
|
||||||
# 4. 创建表空间和用户
|
# 4. 创建表空间和用户
|
||||||
sqlplus system/root@localhost:1521/XE <<EOF
|
sqlplus system/root@localhost:1521/XE <<EOF
|
||||||
CREATE TABLESPACE "rxthinkcmf_thinkphp6_elevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_elevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
CREATE TABLESPACE "rxthinkcmf_thinkphp6_naivevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_naivevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
||||||
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_elevue";
|
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_naivevue";
|
||||||
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
||||||
GRANT UNLIMITED TABLESPACE TO "root";
|
GRANT UNLIMITED TABLESPACE TO "root";
|
||||||
EXIT;
|
EXIT;
|
||||||
@@ -720,15 +720,15 @@ EOF
|
|||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/
|
├── mysql/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # MySQL 版本
|
||||||
├── postgresql/
|
├── postgresql/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/
|
├── sqlserver/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # SQL Server 版本
|
||||||
├── oracle/
|
├── oracle/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # Oracle 版本
|
||||||
└── sqlite/
|
└── sqlite/
|
||||||
└── rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── rxthinkcmf.thinkphp6.naivevue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。
|
各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
├── templates/ ← 代码生成器模板
|
├── templates/ ← 代码生成器模板
|
||||||
├── runtime/ ← 运行时缓存(自动生成)
|
├── runtime/ ← 运行时缓存(自动生成)
|
||||||
├── vendor/ ← Composer 依赖
|
├── vendor/ ← Composer 依赖
|
||||||
├── wiki/ ← 文档站点(VitePress)
|
|
||||||
├── document/ ← 开发参考手册(txt)
|
├── document/ ← 开发参考手册(txt)
|
||||||
├── .env ← 环境变量配置
|
├── .env ← 环境变量配置
|
||||||
└── think ← ThinkPHP 命令行入口
|
└── think ← ThinkPHP 命令行入口
|
||||||
|
|||||||
@@ -81,52 +81,69 @@ src/views/
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<!-- 搜索区 -->
|
<n-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||||
<el-form :model="searchForm" inline>
|
<!-- 搜索区(BasicForm 驱动) -->
|
||||||
<el-form-item label="名称">
|
<BasicForm @register="registerForm" @submit="handleSubmit" @reset="handleReset" />
|
||||||
<el-input v-model="searchForm.name" />
|
</n-card>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="fetchData">搜索</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 操作栏 -->
|
<n-card :bordered="false" class="proCard">
|
||||||
<el-button v-perm="'sys:example:add'" type="primary" @click="handleAdd">新增</el-button>
|
<!-- 操作栏 + 数据表格(BasicTable 驱动,分页内置) -->
|
||||||
|
<BasicTable
|
||||||
<!-- 数据表格 -->
|
:columns="columns"
|
||||||
<el-table :data="tableData">
|
:request="loadDataTable"
|
||||||
<el-table-column prop="id" label="ID" />
|
:row-key="(row) => row.id"
|
||||||
<el-table-column prop="name" label="名称" />
|
ref="basicTableRef"
|
||||||
<el-table-column prop="statusText" label="状态" />
|
:actionColumn="actionColumn"
|
||||||
<el-table-column label="操作">
|
>
|
||||||
<template #default="{ row }">
|
<template #tableTitle>
|
||||||
<el-button v-perm="'sys:example:update'" @click="handleEdit(row)">编辑</el-button>
|
<n-space>
|
||||||
<el-button v-perm="'sys:example:delete'" type="danger" @click="handleDelete(row.id)">删除</el-button>
|
<n-button v-perm="'sys:example:add'" type="primary" @click="handleAdd">
|
||||||
|
新增
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</BasicTable>
|
||||||
</el-table>
|
</n-card>
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<el-pagination v-model:current-page="pageNo" v-model:page-size="pageSize"
|
|
||||||
:total="total" @change="fetchData" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue';
|
||||||
|
import { NButton } from 'naive-ui';
|
||||||
|
import { BasicTable, TableAction } from '@/components/Table';
|
||||||
|
import { BasicForm, useForm } from '@/components/Form';
|
||||||
import { getExamplePage, exampleDelete } from '@/api/tool/example';
|
import { getExamplePage, exampleDelete } from '@/api/tool/example';
|
||||||
|
|
||||||
const tableData = ref([]);
|
const [registerForm, { handleSubmit, handleReset }] = useForm({
|
||||||
const pageNo = ref(1);
|
schemas: [
|
||||||
const pageSize = ref(20);
|
{ field: 'name', label: '名称', component: 'NInput' },
|
||||||
const total = ref(0);
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const fetchData = async () => {
|
const columns = [
|
||||||
const res = await getExamplePage({ pageNo: pageNo.value, pageSize: pageSize.value });
|
{ title: 'ID', key: 'id', width: 80 },
|
||||||
tableData.value = res.records;
|
{ title: '名称', key: 'name' },
|
||||||
total.value = res.total;
|
{ title: '状态', key: 'statusText' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const actionColumn = {
|
||||||
|
width: 200,
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
render(record) {
|
||||||
|
return h(TableAction, {
|
||||||
|
actions: [
|
||||||
|
{ label: '编辑', type: 'warning', onClick: () => handleEdit(record), auth: 'sys:example:update' },
|
||||||
|
{ label: '删除', type: 'error', onClick: () => handleDelete(record.id), auth: 'sys:example:delete' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => fetchData());
|
// 分页数据请求(BasicTable 内置分页)
|
||||||
|
const loadDataTable = async (params) => {
|
||||||
|
const res = await getExamplePage({ ...params });
|
||||||
|
return { records: res.records, total: res.total };
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -312,10 +329,10 @@ src/utils/http/axios/
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<!-- v-perm:单权限 -->
|
<!-- v-perm:单权限 -->
|
||||||
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button>
|
<n-button v-perm="'sys:user:add'" type="primary">新增</n-button>
|
||||||
|
|
||||||
<!-- v-perms:多权限(满足任一即可) -->
|
<!-- v-perms:多权限(满足任一即可) -->
|
||||||
<el-button v-perms="['sys:user:add', 'sys:user:update']">操作</el-button>
|
<n-button v-perms="['sys:user:add', 'sys:user:update']">操作</n-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
无权限时元素直接从 DOM 移除,而非仅隐藏。
|
无权限时元素直接从 DOM 移除,而非仅隐藏。
|
||||||
@@ -331,8 +348,11 @@ src/utils/http/axios/
|
|||||||
| Hook | 文件 | 说明 |
|
| Hook | 文件 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `usePermission` | `hooks/web/usePermission.ts` | 权限判断(hasPermission、hasSomePermission) |
|
| `usePermission` | `hooks/web/usePermission.ts` | 权限判断(hasPermission、hasSomePermission) |
|
||||||
| `useTable` | `hooks/web/useTable.ts` | 表格通用逻辑(分页、搜索、刷新) |
|
| `useMessage` / `useDialog` | `hooks/web/useMessage.ts`、`useDialog.ts` | 消息提示 / 确认弹窗(离散 API,可在非组件上下文调用) |
|
||||||
| `useForm` | `hooks/web/useForm.ts` | 表单通用逻辑(提交、重置、校验) |
|
| `useLoadingBar` | `hooks/web/useLoadingBar.ts` | 顶部加载条 |
|
||||||
|
| `useLockFn` | `utils/useLockFn.ts` | 请求级防重复提交 |
|
||||||
|
| `useForm` | `components/Form/src/hooks/useForm.ts` | 表单通用逻辑(注册、提交、重置、校验) |
|
||||||
|
| 表格逻辑 | `components/Table/src/hooks/*` | BasicTable 内部钩子(分页、数据源、搜索) |
|
||||||
|
|
||||||
### Directives(自定义指令)
|
### Directives(自定义指令)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ 浏览器(Vue3 + ElementPlus) │
|
│ 浏览器(Vue3 + NaiveUI) │
|
||||||
│ Axios HTTP 请求 / Vite 开发代理 │
|
│ Axios HTTP 请求 / Vite 开发代理 │
|
||||||
└──────────────────────────────┬──────────────────────────────────┘
|
└──────────────────────────────┬──────────────────────────────────┘
|
||||||
│
|
│
|
||||||
@@ -238,7 +238,7 @@ DictService 和 ParamService 均采用此架构,大幅减少数据库查询。
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
┌──────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
│ 前端(Vue3 + ElementPlus) │
|
│ 前端(Vue3 + NaiveUI) │
|
||||||
│ Pinia · Vue Router · Axios · TinyMce · ECharts │
|
│ Pinia · Vue Router · Axios · TinyMce · ECharts │
|
||||||
└──────────────────────────┬───────────────────────────────────┘
|
└──────────────────────────┬───────────────────────────────────┘
|
||||||
│ HTTP / JSON
|
│ HTTP / JSON
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
- **自动参数校验**:Logic 层配置 `$validateClass` 后,`add`/`update` 自动调用验证器校验,覆盖所有 CRUD 入口(含导入),新增 CityValidate、TenantValidate,全模块验证器覆盖率 100%
|
- **自动参数校验**:Logic 层配置 `$validateClass` 后,`add`/`update` 自动调用验证器校验,覆盖所有 CRUD 入口(含导入),新增 CityValidate、TenantValidate,全模块验证器覆盖率 100%
|
||||||
- **项目文档体系**:Wiki 文档站点全面改版,覆盖快速入门、模块开发、架构设计、运维部署全场景
|
- **项目文档体系**:文档站点全面改版,覆盖快速入门、模块开发、架构设计、运维部署全场景
|
||||||
- **生产环境部署指南**:Docker 容器化支持,提供 Dockerfile + docker-compose.yml 一键部署
|
- **生产环境部署指南**:Docker 容器化支持,提供 Dockerfile + docker-compose.yml 一键部署
|
||||||
- **系统健康检查接口**:运行状态监控,服务可用性实时检测
|
- **系统健康检查接口**:运行状态监控,服务可用性实时检测
|
||||||
- **多数据库 SQL 脚本**:MySQL / PostgreSQL / SQL Server / Oracle / SQLite 五套完整建表脚本
|
- **多数据库 SQL 脚本**:MySQL / PostgreSQL / SQL Server / Oracle / SQLite 五套完整建表脚本
|
||||||
|
|||||||
@@ -289,10 +289,10 @@ class CaptchaService
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-form-item>
|
<n-form-item>
|
||||||
<el-input v-model="form.captchaCode" placeholder="验证码" />
|
<n-input v-model:value="form.captchaCode" placeholder="验证码" />
|
||||||
<img :src="captchaImage" @click="refreshCaptcha" style="cursor: pointer;" />
|
<img :src="captchaImage" @click="refreshCaptcha" style="cursor: pointer;" />
|
||||||
</el-form-item>
|
</n-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -359,38 +359,39 @@ CAPTCHA_RATE_LIMIT = 20
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-form :model="form" :rules="rules" ref="formRef">
|
<n-form :model="form" :rules="rules" ref="formRef">
|
||||||
<el-form-item label="用户名" prop="username">
|
<n-form-item label="用户名" path="username">
|
||||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
<n-input v-model:value="form.username" placeholder="请输入用户名" />
|
||||||
</el-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<el-form-item label="密码" prop="password">
|
<n-form-item label="密码" path="password">
|
||||||
<el-input v-model="form.password" type="password" placeholder="请输入密码" />
|
<n-input v-model:value="form.password" type="password" placeholder="请输入密码" />
|
||||||
</el-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<el-form-item label="验证码" prop="code">
|
<n-form-item label="验证码" path="code">
|
||||||
<div style="display: flex; gap: 10px;">
|
<div style="display: flex; gap: 10px;">
|
||||||
<el-input v-model="form.code" placeholder="请输入验证码"
|
<n-input v-model:value="form.code" placeholder="请输入验证码"
|
||||||
style="flex: 1;" @keyup.enter="handleLogin" />
|
style="flex: 1;" @keyup.enter="handleLogin" />
|
||||||
<img :src="captchaImage" @click="refreshCaptcha"
|
<img :src="captchaImage" @click="refreshCaptcha"
|
||||||
style="cursor: pointer; height: 40px;" title="点击刷新" />
|
style="cursor: pointer; height: 40px;" title="点击刷新" />
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<n-form-item>
|
||||||
<el-button type="primary" @click="handleLogin" :loading="loading">
|
<n-button type="primary" @click="handleLogin" :loading="loading">
|
||||||
登录
|
登录
|
||||||
</el-button>
|
</n-button>
|
||||||
</el-form-item>
|
</n-form-item>
|
||||||
</el-form>
|
</n-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getCaptcha } from '@/api/common/user';
|
import { getCaptcha } from '@/api/common/user';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { ElMessage } from 'element-plus';
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const message = useMessage();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
@@ -424,11 +425,11 @@ const handleLogin = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await userStore.login(form.value);
|
await userStore.login(form.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();
|
||||||
form.value.code = '';
|
form.value.code = '';
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -217,12 +217,16 @@ class DictController extends BaseController
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// HTTP 响应拦截器
|
// HTTP 响应拦截器
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
|
||||||
|
const $message = useMessage();
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,13 +239,13 @@ axios.interceptors.response.use(response => {
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<!-- 演示模式下禁用删除按钮 -->
|
<!-- 演示模式下禁用删除按钮 -->
|
||||||
<el-button
|
<n-button
|
||||||
type="danger"
|
type="error"
|
||||||
:disabled="isDemoMode"
|
:disabled="isDemoMode"
|
||||||
@click="handleDelete"
|
@click="handleDelete"
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -206,14 +206,11 @@ export function getDictItemList(code: string) {
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
<n-select
|
||||||
<el-option
|
v-model:value="formData.status"
|
||||||
v-for="item in statusOptions"
|
:options="statusOptions"
|
||||||
:key="item.value"
|
placeholder="请选择状态"
|
||||||
:label="item.name"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
/>
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -272,9 +269,9 @@ const { options: statusOptions } = useDict('user_status');
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
<n-tag :type="row.status === 1 ? 'success' : 'error'" bordered>
|
||||||
{{ row.statusText }}
|
{{ row.statusText }}
|
||||||
</el-tag>
|
</n-tag>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -256,14 +256,11 @@ onMounted(async () => {
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-select v-model="form.gender" placeholder="请选择性别">
|
<n-select
|
||||||
<el-option
|
v-model:value="form.gender"
|
||||||
v-for="item in genderOptions"
|
:options="genderOptions"
|
||||||
:key="item.value"
|
placeholder="请选择性别"
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
/>
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,27 +8,30 @@
|
|||||||
|
|
||||||
### 按钮 loading
|
### 按钮 loading
|
||||||
|
|
||||||
ElementPlus 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
|
NaiveUI 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-button
|
<n-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="submitting"
|
:loading="submitting"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
提交
|
提交
|
||||||
</el-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
await exampleAdd(formData.value);
|
await exampleAdd(formData.value);
|
||||||
ElMessage.success('添加成功');
|
message.success('添加成功');
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
}
|
}
|
||||||
@@ -41,12 +44,15 @@ async function handleSubmit() {
|
|||||||
前端提供 `useLockFn` 钩子函数,实现请求级别的防重复提交:
|
前端提供 `useLockFn` 钩子函数,实现请求级别的防重复提交:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { useLockFn } from '@/utils/lock';
|
import { useLockFn } from '@/utils/useLockFn';
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
// 包装提交函数,防止重复点击
|
// 包装提交函数,防止重复点击
|
||||||
const handleSubmit = useLockFn(async () => {
|
const { lockFn: handleSubmit } = useLockFn(async () => {
|
||||||
await exampleAdd(formData.value);
|
await exampleAdd(formData.value);
|
||||||
ElMessage.success('添加成功');
|
message.success('添加成功');
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -353,10 +353,10 @@ class ArticleLogic extends BaseTenantLogic
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<!-- 单权限:拥有 sys:user:add 才显示 -->
|
<!-- 单权限:拥有 sys:user:add 才显示 -->
|
||||||
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button>
|
<n-button v-perm="'sys:user:add'" type="primary">新增</n-button>
|
||||||
|
|
||||||
<!-- 多权限:拥有任一权限即显示 -->
|
<!-- 多权限:拥有任一权限即显示 -->
|
||||||
<el-button v-perms="['sys:user:add', 'sys:user:update']">操作</el-button>
|
<n-button v-perms="['sys:user:add', 'sys:user:update']">操作</n-button>
|
||||||
|
|
||||||
<!-- 无权限的按钮直接从 DOM 移除,而非仅隐藏 -->
|
<!-- 无权限的按钮直接从 DOM 移除,而非仅隐藏 -->
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进入项目目录(以实际解压路径为准)
|
# 进入项目目录(以实际解压路径为准)
|
||||||
cd thinkphp6
|
cd RXThinkCMF_ThinkPhp6_NaiveVue
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip 温馨提示
|
:::tip 温馨提示
|
||||||
@@ -90,7 +90,7 @@ APP_DEBUG = true
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql ; 数据库类型
|
TYPE = mysql ; 数据库类型
|
||||||
HOSTNAME = 127.0.0.1 ; 数据库地址
|
HOSTNAME = 127.0.0.1 ; 数据库地址
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue ; 数据库名
|
DATABASE = rxthinkcmf.thinkphp6.naivevue ; 数据库名
|
||||||
USERNAME = root ; 用户名
|
USERNAME = root ; 用户名
|
||||||
PASSWORD = root ; 密码
|
PASSWORD = root ; 密码
|
||||||
HOSTPORT = 3306 ; 端口
|
HOSTPORT = 3306 ; 端口
|
||||||
@@ -105,8 +105,8 @@ REFRESH_TTL = 604800 ; refresh_token 有效期(秒)
|
|||||||
|
|
||||||
; 文件上传配置
|
; 文件上传配置
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf ; 上传目录(绝对路径)
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads ; 上传目录(绝对路径)
|
||||||
DOMAIN_URL = http://file.thinkphp6.elevue ; 文件访问域名
|
DOMAIN_URL = http://file.thinkphp6.naivevue ; 文件访问域名
|
||||||
```
|
```
|
||||||
|
|
||||||
:::danger 重要提示
|
:::danger 重要提示
|
||||||
@@ -123,11 +123,11 @@ DOMAIN_URL = http://file.thinkphp6.elevue ; 文件访问域名
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本
|
├── mysql/rxthinkcmf.thinkphp6.naivevue.sql # MySQL 版本
|
||||||
├── postgresql/rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
├── postgresql/rxthinkcmf.thinkphp6.naivevue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
├── sqlserver/rxthinkcmf.thinkphp6.naivevue.sql # SQL Server 版本
|
||||||
├── oracle/rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
├── oracle/rxthinkcmf.thinkphp6.naivevue.sql # Oracle 版本
|
||||||
└── sqlite/rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── sqlite/rxthinkcmf.thinkphp6.naivevue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
以 MySQL 为例:
|
以 MySQL 为例:
|
||||||
@@ -142,7 +142,7 @@ mysql -uroot -p
|
|||||||
# 在 MySQL 命令行中执行(创建项目数据库):
|
# 在 MySQL 命令行中执行(创建项目数据库):
|
||||||
# CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
|
# CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
|
||||||
# COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
|
# COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
|
||||||
CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 第 2 步:导入数据库脚本
|
# 第 2 步:导入数据库脚本
|
||||||
@@ -150,7 +150,7 @@ CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLL
|
|||||||
# -uroot -proot → 用户名和密码
|
# -uroot -proot → 用户名和密码
|
||||||
# rxthinkcmf... → 目标数据库名
|
# rxthinkcmf... → 目标数据库名
|
||||||
# < file.sql → 将 SQL 文件作为 mysql 命令的输入
|
# < file.sql → 将 SQL 文件作为 mysql 命令的输入
|
||||||
mysql -uroot -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -uroot -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip 温馨提示
|
:::tip 温馨提示
|
||||||
@@ -234,7 +234,7 @@ pnpm dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
:::tip 温馨提示
|
:::tip 温馨提示
|
||||||
前端默认运行在 `8001` 端口,后端默认 `8000` 端口。`vite.config.ts` 中配置了代理,前端请求 `/api/*` 路径时自动转发到后端 `http://admin.thinkphp6.elevue/api/`。
|
前端默认运行在 `8001` 端口,后端默认 `8000` 端口。`vite.config.ts` 中配置了代理,前端请求 `/api/*` 路径时自动转发到后端 `http://admin.thinkphp6.naivevue/api/`。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 第五步:功能验证
|
## 第五步:功能验证
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ crontab -l
|
|||||||
# /mo 1 → 每 1 分钟执行一次
|
# /mo 1 → 每 1 分钟执行一次
|
||||||
# /f → 强制覆盖同名任务
|
# /f → 强制覆盖同名任务
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp\htdocs\v3\thinkphp6 && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
||||||
|
|
||||||
# 或通过图形界面:Win+R → 输入 taskschd.msc → 回车
|
# 或通过图形界面:Win+R → 输入 taskschd.msc → 回车
|
||||||
```
|
```
|
||||||
@@ -320,10 +320,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.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
|
|
||||||
# 导入初始化数据(项目自带的 SQL 文件)
|
# 导入初始化数据(项目自带的 SQL 文件)
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### PostgreSQL
|
### PostgreSQL
|
||||||
@@ -339,7 +339,7 @@ sudo apt install php8.2-pgsql
|
|||||||
sudo systemctl restart php8.2-fpm
|
sudo systemctl restart php8.2-fpm
|
||||||
|
|
||||||
# 创建数据库(-u postgres 以 postgres 用户执行)
|
# 创建数据库(-u postgres 以 postgres 用户执行)
|
||||||
sudo -u postgres createdb rxthinkcmf.thinkphp6.elevue
|
sudo -u postgres createdb rxthinkcmf.thinkphp6.naivevue
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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.thinkphp6.elevue > backup_$(date +%Y%m%d).sql
|
mysqldump -u root -proot rxthinkcmf.thinkphp6.naivevue > backup_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 压缩备份(推荐 — 可节省 70%+ 磁盘空间)
|
# 压缩备份(推荐 — 可节省 70%+ 磁盘空间)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# | gzip → 将 mysqldump 输出通过管道传给 gzip 压缩
|
# | gzip → 将 mysqldump 输出通过管道传给 gzip 压缩
|
||||||
mysqldump -u root -proot rxthinkcmf.thinkphp6.elevue | gzip > backup_$(date +%Y%m%d).sql.gz
|
mysqldump -u root -proot rxthinkcmf.thinkphp6.naivevue | gzip > backup_$(date +%Y%m%d).sql.gz
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 仅备份表结构(不含数据 — 用于对比表结构变更)
|
# 仅备份表结构(不含数据 — 用于对比表结构变更)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# --no-data → 只导出 CREATE TABLE 语句,不导出数据
|
# --no-data → 只导出 CREATE TABLE 语句,不导出数据
|
||||||
mysqldump -u root -proot --no-data rxthinkcmf.thinkphp6.elevue > schema_$(date +%Y%m%d).sql
|
mysqldump -u root -proot --no-data rxthinkcmf.thinkphp6.naivevue > 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.thinkphp6.elevue > data_$(date +%Y%m%d).sql
|
mysqldump -u root -proot --no-create-info rxthinkcmf.thinkphp6.naivevue > data_$(date +%Y%m%d).sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 自动备份脚本
|
### 自动备份脚本
|
||||||
@@ -52,7 +52,7 @@ mysqldump -u root -proot --no-create-info rxthinkcmf.thinkphp6.elevue > data_$(d
|
|||||||
# 配置参数(按实际环境修改)
|
# 配置参数(按实际环境修改)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
BACKUP_DIR="/backup/mysql" # 备份文件存放目录
|
BACKUP_DIR="/backup/mysql" # 备份文件存放目录
|
||||||
DB_NAME="rxthinkcmf.thinkphp6.elevue" # 要备份的数据库名
|
DB_NAME="rxthinkcmf.thinkphp6.naivevue" # 要备份的数据库名
|
||||||
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) # 当前时间戳(年月日_时分秒)
|
||||||
@@ -141,22 +141,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.thinkphp6.elevue < backup_20260920.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < 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.thinkphp6.elevue
|
gunzip < backup_20260920.sql.gz | mysql -u root -proot rxthinkcmf.thinkphp6.naivevue
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 恢复到新数据库(安全起见 — 先验证再切换)
|
# 恢复到新数据库(安全起见 — 先验证再切换)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 1. 创建临时验证数据库
|
# 1. 创建临时验证数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue_restore`;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue_restore`;"
|
||||||
# 2. 恢复备份到临时库
|
# 2. 恢复备份到临时库
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue_restore < backup_20260920.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue_restore < backup_20260920.sql
|
||||||
# 3. 验证数据无误后,重命名数据库(需停机操作)
|
# 3. 验证数据无误后,重命名数据库(需停机操作)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ tar -xzf /backup/uploads_20260920.tar.gz -C / www/uploads/article/
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
BACKUP_FILE=$1 # 第一个参数:备份文件路径
|
BACKUP_FILE=$1 # 第一个参数:备份文件路径
|
||||||
VERIFY_DB="rxthinkcmf.thinkphp6.elevue_verify" # 临时验证数据库名
|
VERIFY_DB="rxthinkcmf.thinkphp6.naivevue_verify" # 临时验证数据库名
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 创建验证数据库(先删除旧的,再创建新的)
|
# 创建验证数据库(先删除旧的,再创建新的)
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
**数据库**:
|
**数据库**:
|
||||||
|
|
||||||
- [ ] 安装 MySQL 8.0+(或 PostgreSQL / SQL Server / Oracle / SQLite)
|
- [ ] 安装 MySQL 8.0+(或 PostgreSQL / SQL Server / Oracle / SQLite)
|
||||||
- [ ] 创建数据库:`CREATE DATABASE \`rxthinkcmf.thinkphp6.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
- [ ] 创建数据库:`CREATE DATABASE \`rxthinkcmf.thinkphp6.naivevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||||
- [ ] 创建数据库用户并授权
|
- [ ] 创建数据库用户并授权
|
||||||
- [ ] 导入 SQL 脚本:`mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql`
|
- [ ] 导入 SQL 脚本:`mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql`
|
||||||
|
|
||||||
**Redis(可选)**:
|
**Redis(可选)**:
|
||||||
|
|
||||||
@@ -146,7 +146,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.thinkphp6.elevue > /backup/db_$(date +%Y%m%d).sql
|
mysqldump -u root -proot rxthinkcmf.thinkphp6.naivevue > /backup/db_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 第 2 步:更新代码
|
# 第 2 步:更新代码
|
||||||
@@ -157,7 +157,7 @@ composer install --no-dev --optimize-autoloader # 安装依赖(排除开
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 第 3 步:更新数据库(如有迁移脚本)
|
# 第 3 步:更新数据库(如有迁移脚本)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/migration.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/migration.sql
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 第 4 步:清除缓存
|
# 第 4 步:清除缓存
|
||||||
@@ -199,7 +199,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.thinkphp6.elevue < /backup/db_20260920.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < /backup/db_20260920.sql
|
||||||
|
|
||||||
# 重启 PHP-FPM(清除 OPcache,加载旧代码)
|
# 重启 PHP-FPM(清除 OPcache,加载旧代码)
|
||||||
sudo systemctl restart php8.2-fpm
|
sudo systemctl restart php8.2-fpm
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本(约 78 万行)
|
├── mysql/rxthinkcmf.thinkphp6.naivevue.sql # MySQL 版本(约 78 万行)
|
||||||
├── postgresql/rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
├── postgresql/rxthinkcmf.thinkphp6.naivevue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
├── sqlserver/rxthinkcmf.thinkphp6.naivevue.sql # SQL Server 版本
|
||||||
├── oracle/rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
├── oracle/rxthinkcmf.thinkphp6.naivevue.sql # Oracle 版本
|
||||||
└── sqlite/rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── sqlite/rxthinkcmf.thinkphp6.naivevue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入。
|
各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入。
|
||||||
@@ -28,7 +28,7 @@ document/
|
|||||||
# -u root -proot → 用户名和密码
|
# -u root -proot → 用户名和密码
|
||||||
# rxthinkcmf... → 目标数据库名
|
# rxthinkcmf... → 目标数据库名
|
||||||
# < file.sql → 将 SQL 文件作为输入执行
|
# < file.sql → 将 SQL 文件作为输入执行
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### PostgreSQL
|
### PostgreSQL
|
||||||
@@ -40,7 +40,7 @@ mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thi
|
|||||||
# -U postgres → 连接用户
|
# -U postgres → 连接用户
|
||||||
# -d ... → 目标数据库
|
# -d ... → 目标数据库
|
||||||
# -f file.sql → 执行 SQL 文件
|
# -f file.sql → 执行 SQL 文件
|
||||||
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -f document/postgresql/rxthinkcmf.thinkphp6.elevue.sql
|
psql -U postgres -d rxthinkcmf.thinkphp6.naivevue -f document/postgresql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### SQL Server
|
### SQL Server
|
||||||
@@ -54,7 +54,7 @@ psql -U postgres -d rxthinkcmf.thinkphp6.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.thinkphp6.elevue -i document/sqlserver/rxthinkcmf.thinkphp6.elevue.sql
|
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.naivevue -i document/sqlserver/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### Oracle
|
### Oracle
|
||||||
@@ -65,7 +65,7 @@ sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.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.thinkphp6.elevue.sql
|
sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### SQLite
|
### SQLite
|
||||||
@@ -76,7 +76,7 @@ sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.thinkphp6.elevue
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 数据库文件路径 → SQLite 数据库存储在单个文件中
|
# 数据库文件路径 → SQLite 数据库存储在单个文件中
|
||||||
# < file.sql → 将 SQL 文件作为输入执行
|
# < file.sql → 将 SQL 文件作为输入执行
|
||||||
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.elevue.sql
|
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
thinkphp6/
|
|
||||||
├── docker/
|
├── docker/
|
||||||
│ ├── Dockerfile # PHP-FPM 应用镜像定义
|
│ ├── Dockerfile # PHP-FPM 应用镜像定义
|
||||||
│ ├── nginx.conf # Nginx 站点配置
|
│ ├── nginx.conf # Nginx 站点配置
|
||||||
@@ -323,7 +322,7 @@ services:
|
|||||||
# root 用户密码(必须 8 位以上,包含大小写字母和数字)
|
# root 用户密码(必须 8 位以上,包含大小写字母和数字)
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||||
# 启动时自动创建的数据库
|
# 启动时自动创建的数据库
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.thinkphp6.elevue}
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.thinkphp6.naivevue}
|
||||||
# 字符集(支持 emoji 等四字节字符)
|
# 字符集(支持 emoji 等四字节字符)
|
||||||
MYSQL_CHARSET: utf8mb4
|
MYSQL_CHARSET: utf8mb4
|
||||||
# 排序规则
|
# 排序规则
|
||||||
@@ -332,7 +331,7 @@ services:
|
|||||||
# 数据持久化(容器重建不丢数据)
|
# 数据持久化(容器重建不丢数据)
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
# 初始化 SQL(首次启动时自动执行,仅在数据卷为空时生效)
|
# 初始化 SQL(首次启动时自动执行,仅在数据卷为空时生效)
|
||||||
- ./document/mysql/rxthinkcmf.thinkphp6.elevue.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
- ./document/mysql/rxthinkcmf.thinkphp6.naivevue.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
|
||||||
@@ -389,7 +388,7 @@ networks:
|
|||||||
```ini
|
```ini
|
||||||
# Docker 环境变量(密码等敏感信息,切勿提交 Git)
|
# Docker 环境变量(密码等敏感信息,切勿提交 Git)
|
||||||
MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024
|
MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024
|
||||||
MYSQL_DATABASE=rxthinkcmf.thinkphp6.elevue
|
MYSQL_DATABASE=rxthinkcmf.thinkphp6.naivevue
|
||||||
REDIS_PASSWORD=Your_Redis_Password_2024
|
REDIS_PASSWORD=Your_Redis_Password_2024
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
```
|
```
|
||||||
@@ -491,16 +490,16 @@ docker-compose exec app bash
|
|||||||
docker-compose exec mysql bash
|
docker-compose exec mysql bash
|
||||||
|
|
||||||
# 在容器内执行 SQL
|
# 在容器内执行 SQL
|
||||||
docker-compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue
|
docker-compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.naivevue
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# 数据库操作
|
# 数据库操作
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# 导入 SQL 文件
|
# 导入 SQL 文件
|
||||||
docker-compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
docker-compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
|
|
||||||
# 导出数据库备份
|
# 导出数据库备份
|
||||||
docker-compose exec mysql mysqldump -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue > backup_$(date +%Y%m%d).sql
|
docker-compose exec mysql mysqldump -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.naivevue > backup_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# ThinkPHP 命令
|
# ThinkPHP 命令
|
||||||
|
|||||||
@@ -132,8 +132,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.thinkphp6.elevue.* → 该项目数据库的所有表
|
-- rxthinkcmf.thinkphp6.naivevue.* → 该项目数据库的所有表
|
||||||
GRANT ALL PRIVILEGES ON `rxthinkcmf.thinkphp6.elevue`.* TO 'rxthinkcmf_app'@'localhost';
|
GRANT ALL PRIVILEGES ON `rxthinkcmf.thinkphp6.naivevue`.* TO 'rxthinkcmf_app'@'localhost';
|
||||||
FLUSH PRIVILEGES; -- 刷新权限使配置生效
|
FLUSH PRIVILEGES; -- 刷新权限使配置生效
|
||||||
|
|
||||||
-- 禁止远程 root 登录(只允许本地连接)
|
-- 禁止远程 root 登录(只允许本地连接)
|
||||||
|
|||||||
@@ -97,7 +97,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.thinkphp6.elevue\`.* TO 'root'@'%' IDENTIFIED BY 'root';"
|
mysql -u root -proot -e "GRANT ALL ON \`rxthinkcmf.thinkphp6.naivevue\`.* TO 'root'@'%' IDENTIFIED BY 'root';"
|
||||||
mysql -u root -p -e "FLUSH PRIVILEGES;" # 刷新权限使配置生效
|
mysql -u root -p -e "FLUSH PRIVILEGES;" # 刷新权限使配置生效
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ sudo systemctl status postgresql
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 测试连接(-U 指定用户,-d 指定数据库,-c 执行 SQL)
|
# 测试连接(-U 指定用户,-d 指定数据库,-c 执行 SQL)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -c "SELECT 1;"
|
psql -U postgres -d rxthinkcmf.thinkphp6.naivevue -c "SELECT 1;"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 检查客户端认证配置(pg_hba.conf 控制哪些 IP 可以连接)
|
# 检查客户端认证配置(pg_hba.conf 控制哪些 IP 可以连接)
|
||||||
@@ -169,14 +169,14 @@ php -m | grep pdo_sqlsrv # 查找 pdo_sqlsrv 扩展
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# MySQL:通过 mysql 命令导入 SQL 文件
|
# MySQL:通过 mysql 命令导入 SQL 文件
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
|
|
||||||
# PostgreSQL:通过 psql 命令导入 SQL 文件
|
# PostgreSQL:通过 psql 命令导入 SQL 文件
|
||||||
# -U 指定用户,-d 指定数据库,-f 指定 SQL 文件
|
# -U 指定用户,-d 指定数据库,-f 指定 SQL 文件
|
||||||
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -f document/postgresql/rxthinkcmf.thinkphp6.elevue.sql
|
psql -U postgres -d rxthinkcmf.thinkphp6.naivevue -f document/postgresql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
|
|
||||||
# SQLite:通过 sqlite3 命令导入 SQL 文件
|
# SQLite:通过 sqlite3 命令导入 SQL 文件
|
||||||
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.elevue.sql
|
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## PHP 问题
|
## PHP 问题
|
||||||
@@ -268,13 +268,13 @@ grep JWT.SECRET .env
|
|||||||
# 检查用户角色分配(用户是否分配了角色)
|
# 检查用户角色分配(用户是否分配了角色)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# think_user_role 表存储用户与角色的关联关系
|
# think_user_role 表存储用户与角色的关联关系
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue -e "SELECT * FROM think_user_role WHERE user_id=1;"
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue -e "SELECT * FROM think_user_role WHERE user_id=1;"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 检查角色权限(角色是否分配了菜单权限)
|
# 检查角色权限(角色是否分配了菜单权限)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# think_role_menu 表存储角色与菜单的关联关系
|
# think_role_menu 表存储角色与菜单的关联关系
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue -e "SELECT * FROM think_role_menu WHERE role_id=1;"
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue -e "SELECT * FROM think_role_menu WHERE role_id=1;"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 文件上传失败
|
### 文件上传失败
|
||||||
|
|||||||
@@ -15,14 +15,13 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
├── app/ // 后端源码
|
├── app/ // 后端源码
|
||||||
├── ui/ // 前端源码(Vue3 + ElementPlus)
|
├── ui/ // 前端源码(Vue3 + NaiveUI)
|
||||||
├── config/ // 配置文件
|
├── config/ // 配置文件
|
||||||
├── document/ // 项目文档(SQL脚本、开发手册等)
|
├── document/ // 项目文档(SQL脚本、开发手册等)
|
||||||
├── extend/ // 扩展类库
|
├── extend/ // 扩展类库
|
||||||
├── public/ // 公共资源(入口文件、上传目录)
|
├── public/ // 公共资源(入口文件、上传目录)
|
||||||
├── route/ // 路由定义
|
├── route/ // 路由定义
|
||||||
├── templates/ // 代码生成模板
|
├── templates/ // 代码生成模板
|
||||||
├── wiki/ // VitePress 文档站点
|
|
||||||
├── vendor/ // Composer 依赖(不提交到版本控制)
|
├── vendor/ // Composer 依赖(不提交到版本控制)
|
||||||
├── runtime/ // 运行时缓存(不提交到版本控制)
|
├── runtime/ // 运行时缓存(不提交到版本控制)
|
||||||
├── .env.example // 环境变量模板
|
├── .env.example // 环境变量模板
|
||||||
|
|||||||
@@ -104,8 +104,10 @@ export function exampleBatchDelete(data: any) {
|
|||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getExamplePage, exampleDelete } from '@/api/tool/example';
|
import { getExamplePage, exampleDelete } from '@/api/tool/example';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const dialog = useDialog();
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
|
|
||||||
// 查询
|
// 查询
|
||||||
@@ -116,10 +118,17 @@ const fetchData = async () => {
|
|||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
await ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' });
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确认删除?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
await exampleDelete(id);
|
await exampleDelete(id);
|
||||||
ElMessage.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchData();
|
fetchData();
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,31 +8,53 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicTable @register="registerTable" />
|
<BasicTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="basicTableRef"
|
||||||
|
:actionColumn="actionColumn"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicTable, useTable } from '@/components/Table';
|
import { h } from 'vue';
|
||||||
|
import { BasicTable, TableAction } from '@/components/Table';
|
||||||
|
import { getExamplePage } from '@/api/tool/example';
|
||||||
|
|
||||||
const [registerTable] = useTable({
|
const columns = [ // 列定义(NaiveUI 用 title/key)
|
||||||
api: getExamplePage, // 数据接口
|
{ title: 'ID', key: 'id', width: 80 },
|
||||||
columns: [ // 列定义
|
{ title: '名称', key: 'name' },
|
||||||
{ title: 'ID', field: 'id', width: 80 },
|
{ title: '状态', key: 'statusText', width: 100 },
|
||||||
{ title: '名称', field: 'name' },
|
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||||
{ title: '状态', field: 'statusText', width: 100 },
|
];
|
||||||
{ title: '创建时间', field: 'createTime', width: 180 },
|
|
||||||
],
|
const actionColumn = { // 操作列(render 返回 TableAction)
|
||||||
actionColumn: { // 操作列
|
|
||||||
width: 200,
|
width: 200,
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
render(record) {
|
||||||
|
return h(TableAction, {
|
||||||
actions: [
|
actions: [
|
||||||
{ label: '编辑', onClick: handleEdit },
|
{ label: '编辑', type: 'warning', onClick: () => handleEdit(record) },
|
||||||
{ label: '删除', onClick: handleDelete, type: 'danger' },
|
{ label: '删除', type: 'error', onClick: () => handleDelete(record.id), auth: ['sys:example:delete'] },
|
||||||
],
|
],
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页数据请求(BasicTable 内置分页)
|
||||||
|
const loadDataTable = async (params) => {
|
||||||
|
return await getExamplePage({ ...params });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::tip 说明
|
||||||
|
`BasicTable` 通过 `:columns`、`:request`、`ref` 驱动,`TableAction` 用于渲染操作列;刷新表格调用 `basicTableRef.value.reload()`。
|
||||||
|
:::
|
||||||
|
|
||||||
## Page 页面容器
|
## Page 页面容器
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ VITE_GLOB_API_URL =
|
|||||||
VITE_GLOB_API_URL_PREFIX = /api
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|
||||||
# 代理配置(JSON 数组格式:[["前缀","目标地址"]])
|
# 代理配置(JSON 数组格式:[["前缀","目标地址"]])
|
||||||
VITE_PROXY = [["/api","http://admin.thinkphp6.elevue/api"]]
|
VITE_PROXY = [["/api","http://admin.thinkphp6.naivevue/api"]]
|
||||||
|
|
||||||
# 上传接口地址
|
# 上传接口地址
|
||||||
VITE_GLOB_UPLOAD_URL = /api/upload
|
VITE_GLOB_UPLOAD_URL = /api/upload
|
||||||
|
|
||||||
# 图片访问地址(用于预览上传的图片)
|
# 图片访问地址(用于预览上传的图片)
|
||||||
VITE_GLOB_IMG_URL = http://file.thinkphp6.elevue
|
VITE_GLOB_IMG_URL = http://file.thinkphp6.naivevue
|
||||||
|
|
||||||
# 是否在生产环境删除 console(开发环境保留)
|
# 是否在生产环境删除 console(开发环境保留)
|
||||||
VITE_DROP_CONSOLE = false
|
VITE_DROP_CONSOLE = false
|
||||||
@@ -118,7 +118,7 @@ VITE_GLOB_API_URL =
|
|||||||
### VITE_PROXY — 开发代理配置
|
### VITE_PROXY — 开发代理配置
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VITE_PROXY = [["/api","http://admin.thinkphp6.elevue/api"]]
|
VITE_PROXY = [["/api","http://admin.thinkphp6.naivevue/api"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
**格式:** JSON 数组,每项为 `[前缀, 目标地址]`
|
**格式:** JSON 数组,每项为 `[前缀, 目标地址]`
|
||||||
@@ -151,7 +151,7 @@ VITE_GLOB_UPLOAD_URL = /api/upload
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 开发环境
|
# 开发环境
|
||||||
VITE_GLOB_IMG_URL = http://file.thinkphp6.elevue
|
VITE_GLOB_IMG_URL = http://file.thinkphp6.naivevue
|
||||||
|
|
||||||
# 生产环境
|
# 生产环境
|
||||||
VITE_GLOB_IMG_URL = https://cdn.example.com
|
VITE_GLOB_IMG_URL = https://cdn.example.com
|
||||||
@@ -199,53 +199,59 @@ const fullApiUrl = apiUrl + apiPrefix; // 如 "/api" 或 "https://api.example.c
|
|||||||
### `vite.config.ts`
|
### `vite.config.ts`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { defineConfig, loadEnv } from 'vite';
|
// ui/vite.config.ts(节选)
|
||||||
|
import { loadEnv } from 'vite';
|
||||||
|
import { createProxy } from './build/vite/proxy';
|
||||||
|
import { wrapperEnv } from './build/utils';
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default ({ command, mode }) => {
|
||||||
// 加载环境变量
|
const root = process.cwd();
|
||||||
const env = loadEnv(mode, process.cwd());
|
const env = loadEnv(mode, root);
|
||||||
|
const viteEnv = wrapperEnv(env);
|
||||||
|
const { VITE_PUBLIC_PATH, VITE_PORT, VITE_PROXY } = viteEnv;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 开发服务器
|
base: VITE_PUBLIC_PATH,
|
||||||
|
|
||||||
|
// Less 全局变量注入
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
less: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
additionalData: `@import "src/styles/var.less";`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 开发服务器(代理由 VITE_PROXY 环境变量驱动)
|
||||||
server: {
|
server: {
|
||||||
port: Number(env.VITE_PORT) || 8001,
|
host: true,
|
||||||
proxy: {
|
port: VITE_PORT,
|
||||||
'/api': {
|
proxy: createProxy(VITE_PROXY),
|
||||||
target: 'http://admin.thinkphp6.elevue/api',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 构建配置
|
// 构建配置
|
||||||
build: {
|
build: {
|
||||||
// 生产环境删除 console
|
target: 'es2015',
|
||||||
drop: env.VITE_DROP_CONSOLE === 'true' ? ['console', 'debugger'] : [],
|
outDir: 'dist',
|
||||||
// 启用 gzip 压缩
|
chunkSizeWarningLimit: 2000,
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
manualChunks: {
|
|
||||||
vue: ['vue', 'vue-router', 'pinia'],
|
|
||||||
element: ['element-plus'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**代理实现**(`build/vite/proxy.ts`):读取 `VITE_PROXY` 数组,为每个前缀生成代理规则(`changeOrigin: true` + 去前缀 rewrite):
|
||||||
|
|
||||||
### 代理配置详解
|
### 代理配置详解
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 开发环境代理配置
|
// build/vite/proxy.ts — 由 VITE_PROXY = [["/api","http://admin.thinkphp6.naivevue/api"]] 生成
|
||||||
server: {
|
{
|
||||||
proxy: {
|
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://admin.thinkphp6.elevue/api', // 目标地址
|
target: 'http://admin.thinkphp6.naivevue/api', // 目标地址
|
||||||
changeOrigin: true, // 修改请求头 Origin
|
changeOrigin: true, // 修改请求头 Origin
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''), // 路径重写(可选)
|
ws: true, // 支持 WebSocket
|
||||||
},
|
rewrite: (path) => path.replace(new RegExp('^/api'), ''), // 去掉前缀
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -257,7 +263,7 @@ server: {
|
|||||||
│
|
│
|
||||||
▼ Vite proxy
|
▼ Vite proxy
|
||||||
│
|
│
|
||||||
▼ 转发到: http://admin.thinkphp6.elevue/api/user/page
|
▼ 转发到: http://admin.thinkphp6.naivevue/api/user/page
|
||||||
```
|
```
|
||||||
|
|
||||||
## 构建命令
|
## 构建命令
|
||||||
@@ -290,8 +296,7 @@ ui/dist/
|
|||||||
├── assets/
|
├── assets/
|
||||||
│ ├── 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(分包)
|
|
||||||
├── favicon.ico
|
├── favicon.ico
|
||||||
└── static/ # 静态资源
|
└── static/ # 静态资源
|
||||||
```
|
```
|
||||||
@@ -342,7 +347,7 @@ server {
|
|||||||
# .env.staging
|
# .env.staging
|
||||||
VITE_GLOB_API_URL =
|
VITE_GLOB_API_URL =
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
VITE_GLOB_IMG_URL = https://test-cdn.example.com
|
VITE_GLOB_IMG_URL = http://file.thinkphp6.naivevue
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -387,4 +392,4 @@ pnpm build --mode staging
|
|||||||
|
|
||||||
**原因:** 未启用 gzip 压缩或未分包。
|
**原因:** 未启用 gzip 压缩或未分包。
|
||||||
|
|
||||||
**解决:** 启用 `VITE_BUILD_COMPRESS = gzip`,配置 `manualChunks` 分包。
|
**解决:** 启用 `VITE_BUILD_COMPRESS = gzip`(构建产物生成 `.gz`,配合 Nginx `gzip_static on`);如仍需拆分 chunk,可在 `vite.config.ts` 的 `build.rollupOptions.output.manualChunks` 中配置分包。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-button @click="handleImport">导入</el-button>
|
<n-button @click="handleImport">导入</n-button>
|
||||||
<ImportExcel @success="onImportSuccess" />
|
<ImportExcel @success="onImportSuccess" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ const [registerForm] = useForm({
|
|||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
label: '名称',
|
label: '名称',
|
||||||
component: 'Input',
|
component: 'NInput', // 组件名对应 naive-ui 的 NInput
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
label: '状态',
|
label: '状态',
|
||||||
component: 'Select',
|
component: 'NSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: [
|
options: [
|
||||||
{ label: '启用', value: 1 },
|
{ label: '启用', value: 1 },
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ config.headers['Authorization'] = `Bearer ${token}`;
|
|||||||
### 响应拦截器(统一错误处理)
|
### 响应拦截器(统一错误处理)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 响应处理逻辑
|
// 响应处理逻辑(src/utils/http/axios/index.ts)
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
|
||||||
|
const $message = useMessage();
|
||||||
const { code, data, msg } = res.data;
|
const { code, data, msg } = res.data;
|
||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
@@ -43,7 +46,7 @@ if (code === 401) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 其他错误,显示提示
|
// 其他错误,显示提示
|
||||||
ElMessage.error(msg || '操作失败');
|
$message.error(msg || '操作失败');
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip 响应处理规则
|
:::tip 响应处理规则
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 基础翻译 -->
|
<!-- 基础翻译 -->
|
||||||
<el-button>{{ $t('common.search') }}</el-button>
|
<n-button>{{ $t('common.search') }}</n-button>
|
||||||
<el-button>{{ $t('common.reset') }}</el-button>
|
<n-button>{{ $t('common.reset') }}</n-button>
|
||||||
|
|
||||||
<!-- 带参数翻译 -->
|
<!-- 带参数翻译 -->
|
||||||
<p>{{ $t('message.welcome', { name: 'Admin' }) }}</p>
|
<p>{{ $t('message.welcome', { name: 'Admin' }) }}</p>
|
||||||
@@ -134,24 +134,24 @@ const columns = computed(() => [
|
|||||||
```vue
|
```vue
|
||||||
<!-- ui/src/components/LangSwitch/index.vue -->
|
<!-- ui/src/components/LangSwitch/index.vue -->
|
||||||
<template>
|
<template>
|
||||||
<el-dropdown @command="switchLang">
|
<n-dropdown :options="langOptions" @select="switchLang">
|
||||||
<el-button :icon="Icon" circle />
|
<n-button>语言</n-button>
|
||||||
<template #dropdown>
|
</n-dropdown>
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item command="zh-CN">中文</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="en-US">English</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useLocaleStore } from '@/store/modules/locale';
|
import { useLocaleStore } from '@/store/modules/locale';
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
const localeStore = useLocaleStore();
|
const localeStore = useLocaleStore();
|
||||||
|
|
||||||
|
const langOptions = [
|
||||||
|
{ label: '中文', key: 'zh-CN' },
|
||||||
|
{ label: 'English', key: 'en-US' },
|
||||||
|
];
|
||||||
|
|
||||||
const switchLang = (lang: string) => {
|
const switchLang = (lang: string) => {
|
||||||
locale.value = lang;
|
locale.value = lang;
|
||||||
localeStore.setLocale(lang);
|
localeStore.setLocale(lang);
|
||||||
|
|||||||
@@ -17,45 +17,98 @@
|
|||||||
## 登录页面代码
|
## 登录页面代码
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<!-- src/views/login/index.vue -->
|
<!-- src/views/login/LoginForm.vue(节选) -->
|
||||||
<template>
|
<template>
|
||||||
<el-form :model="loginForm" @submit.prevent="handleLogin">
|
<n-form
|
||||||
<el-form-item>
|
ref="formRef"
|
||||||
<el-input v-model="loginForm.username" placeholder="用户名" />
|
:model="formInline"
|
||||||
</el-form-item>
|
:rules="rules"
|
||||||
<el-form-item>
|
:show-label="false"
|
||||||
<el-input v-model="loginForm.password" type="password" placeholder="密码" />
|
size="large"
|
||||||
</el-form-item>
|
>
|
||||||
<el-form-item>
|
<n-form-item path="username">
|
||||||
<el-input v-model="loginForm.captchaCode" placeholder="验证码" />
|
<n-input v-model:value="formInline.username" placeholder="请输入登录账号" />
|
||||||
<img :src="captchaImage" @click="refreshCaptcha" />
|
</n-form-item>
|
||||||
</el-form-item>
|
<n-form-item path="password">
|
||||||
<el-button type="primary" @click="handleLogin">登录</el-button>
|
<n-input
|
||||||
</el-form>
|
v-model:value="formInline.password"
|
||||||
|
type="password"
|
||||||
|
show-password-on="mousedown"
|
||||||
|
placeholder="请输入登录密码"
|
||||||
|
@keyup.enter="handleSubmit"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item path="code">
|
||||||
|
<div style="display: flex">
|
||||||
|
<n-input
|
||||||
|
v-model:value.trim="formInline.code"
|
||||||
|
placeholder="验证码"
|
||||||
|
@keyup.enter="handleSubmit"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
style="width: 108px; height: 40px; margin-left: 8px; cursor: pointer"
|
||||||
|
@click="getCaptcha"
|
||||||
|
v-if="captchaImg"
|
||||||
|
:src="captchaImg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<n-form-item>
|
||||||
|
<n-button
|
||||||
|
class="w-full"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
:loading="loading"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</n-button>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useMessage, type FormRules } from 'naive-ui';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { getCaptcha } from '@/api/common/user';
|
import { getInfoCaptcha } from '@/api/common/user';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const message = useMessage();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const loginForm = ref({ username: '', password: '', captchaCode: '', captchaKey: '' });
|
|
||||||
const captchaImage = ref('');
|
const formInline = reactive({ username: '', password: '', code: '' });
|
||||||
|
const rules: FormRules = {
|
||||||
|
username: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
|
||||||
|
password: [{ required: true, message: '请输入登录密码', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const captchaImg = ref('');
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
const refreshCaptcha = async () => {
|
const getCaptcha = async () => {
|
||||||
const res = await getCaptcha();
|
const res = await getInfoCaptcha();
|
||||||
loginForm.value.captchaKey = res.key;
|
captchaImg.value = res.image;
|
||||||
captchaImage.value = res.image;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const handleLogin = async () => {
|
const handleSubmit = async () => {
|
||||||
await userStore.login(loginForm.value);
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await userStore.login(formInline);
|
||||||
router.push('/');
|
router.push('/');
|
||||||
|
} catch (e) {
|
||||||
|
message.error(e.message || '登录失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => refreshCaptcha());
|
onMounted(() => getCaptcha());
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 9.12 新增页面开发步骤
|
# 9.12 新增页面开发步骤
|
||||||
|
|
||||||
:::tip 概述
|
:::tip 概述
|
||||||
以 example 模块为例,演示从 API 到视图的完整开发步骤。一个完整模块涉及 6 个文件:API 接口、表格列配置、查询表单配置、列表页、编辑弹窗、详情弹窗。
|
以 example 模块为例,演示从 API 到视图的完整开发步骤(代码来自项目实际的 `tool/example` 模块)。一个完整模块涉及 6 个文件:API 接口、表格列配置、查询表单配置、列表页、编辑弹窗、详情弹窗。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 开发流程
|
## 开发流程
|
||||||
@@ -90,64 +90,60 @@ export function exampleBatchDelete(data: any) {
|
|||||||
|
|
||||||
## 步骤二:创建表格列配置
|
## 步骤二:创建表格列配置
|
||||||
|
|
||||||
|
表格列使用 `title` / `key` 字段声明表头与数据字段,`render` 函数参数为当前行对象:
|
||||||
|
|
||||||
```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 { NTag, NAvatar } from 'naive-ui';
|
||||||
|
|
||||||
export const columns = [
|
export const columns = [
|
||||||
{
|
{
|
||||||
type: 'selection', // 多选框列
|
title: '案例名称',
|
||||||
|
key: 'name',
|
||||||
|
width: 120,
|
||||||
|
// 普通文本渲染(默认按 key 取值)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ID',
|
title: '案例图片',
|
||||||
prop: 'id',
|
key: 'avatar',
|
||||||
fixed: 'left',
|
width: 120,
|
||||||
width: 50,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '名称',
|
|
||||||
prop: 'name',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
prop: 'statusText',
|
|
||||||
minWidth: 100,
|
|
||||||
// 选项标签渲染:按值映射文字与颜色
|
|
||||||
render(record) {
|
|
||||||
const statusMap = {
|
|
||||||
0: { text: '禁用', type: 'danger' },
|
|
||||||
1: { text: '启用', type: 'success' },
|
|
||||||
};
|
|
||||||
const status = statusMap[record.row.status];
|
|
||||||
return h(ElTag, { type: status?.type || 'info' }, {
|
|
||||||
default: () => status?.text || record.row.statusText,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '头像',
|
|
||||||
prop: 'avatar',
|
|
||||||
minWidth: 80,
|
|
||||||
// 图片渲染:以方形头像形式展示
|
// 图片渲染:以方形头像形式展示
|
||||||
render(record) {
|
render(record) {
|
||||||
return h(ElAvatar, {
|
return h(NAvatar, {
|
||||||
size: 48,
|
size: 48,
|
||||||
src: record.row.avatar,
|
src: record.avatar,
|
||||||
shape: 'square',
|
round: false,
|
||||||
fit: 'fill',
|
objectFit: 'fill',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '创建人',
|
title: '案例状态',
|
||||||
prop: 'createUser',
|
key: 'status',
|
||||||
minWidth: 100,
|
width: 100,
|
||||||
|
// 选项标签渲染:按选项值映射文字与标签颜色
|
||||||
|
render(record) {
|
||||||
|
const statusMap = {
|
||||||
|
1: { text: '在用', type: 'success' },
|
||||||
|
2: { text: '停用', type: 'error' },
|
||||||
|
};
|
||||||
|
const status = statusMap[record.status];
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{ bordered: false, type: status?.type || 'info' },
|
||||||
|
{ default: () => status?.text || record.status },
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '创建时间',
|
title: '创建人',
|
||||||
prop: 'createTime',
|
key: 'createUser',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createTime',
|
||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -158,39 +154,42 @@ export const columns = [
|
|||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `type` | string | 列类型:`selection`(多选框)/ `index`(序号) |
|
| `type` | string | 列类型:`selection`(多选框)/ `index`(序号) |
|
||||||
| `label` | string | 表头标题 |
|
| `title` | string | 表头标题 |
|
||||||
| `prop` | string | 数据字段名(camelCase) |
|
| `key` | string | 数据字段名(camelCase) |
|
||||||
| `fixed` | string | 固定列:`left` / `right` |
|
| `fixed` | string | 固定列:`left` / `right` |
|
||||||
| `width` | number | 固定宽度 |
|
| `width` | number | 固定宽度 |
|
||||||
| `minWidth` | number | 最小宽度(自适应) |
|
| `minWidth` | number | 最小宽度(自适应) |
|
||||||
| `render` | function | 自定义渲染函数 |
|
| `render` | function | 自定义渲染函数,参数为当前行对象 |
|
||||||
|
| `auth` | string[] | 按权限控制列显示(可选) |
|
||||||
|
|
||||||
## 步骤三:创建查询表单配置
|
## 步骤三:创建查询表单配置
|
||||||
|
|
||||||
|
`component` 取值为对应的组件名(`NInput`/`NSelect`/`NInputNumber`/`NDatePicker` 等):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ui/src/views/tool/example/querySchemas.ts
|
// ui/src/views/tool/example/querySchemas.ts(来自项目实际代码)
|
||||||
import { FormSchema } from '@/components/Form/index';
|
import { FormSchema } from '@/components/Form/index';
|
||||||
|
|
||||||
export const schemas: FormSchema[] = [
|
export const schemas: FormSchema[] = [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
component: 'Input',
|
component: 'NInput',
|
||||||
label: '名称',
|
label: '案例名称',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入名称',
|
placeholder: '请输入案例名称',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
component: 'Select',
|
component: 'NSelect',
|
||||||
label: '状态',
|
label: '案例状态',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请选择状态',
|
placeholder: '请选择案例状态',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: '启用', value: 1 },
|
{ label: '在用', value: 1 },
|
||||||
{ label: '禁用', value: 0 },
|
{ label: '停用', value: 2 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -201,293 +200,347 @@ export const schemas: FormSchema[] = [
|
|||||||
|
|
||||||
| component | 适用场景 | 说明 |
|
| component | 适用场景 | 说明 |
|
||||||
|-----------|---------|------|
|
|-----------|---------|------|
|
||||||
| `Input` | 字符串字段 | 文本输入框 |
|
| `NInput` | 字符串字段 | 文本输入框 |
|
||||||
| `Select` | 枚举/选项字段 | 下拉选择框 |
|
| `NSelect` | 枚举/选项字段 | 下拉选择框(选项通过 `componentProps.options` 传入) |
|
||||||
| `InputNumber` | 数字字段 | 数字输入框 |
|
| `NInputNumber` | 数字字段 | 数字输入框 |
|
||||||
| `DatePicker` | 日期字段 | 日期选择器 |
|
| `NDatePicker` | 日期字段 | 日期选择器 |
|
||||||
|
| `NRadioGroup` | 少量选项(≤3 个) | 单选组 |
|
||||||
|
|
||||||
## 步骤四:创建列表页
|
## 步骤四:创建列表页
|
||||||
|
|
||||||
|
列表页使用 `BasicForm`(搜索)+ `BasicTable`(表格,内置分页与操作列)组合:
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<!-- ui/src/views/tool/example/index.vue -->
|
<!-- ui/src/views/tool/example/index.vue(节选,来自项目实际代码) -->
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 查询表单 -->
|
<!-- 查询表单 -->
|
||||||
<SearchForm :schemas="querySchemas" @search="handleSearch" @reset="handleReset" />
|
<n-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||||
|
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||||
<!-- 操作栏 -->
|
</n-card>
|
||||||
<el-row :gutter="10" style="margin-bottom: 10px;">
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="primary" @click="handleAdd">新增</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="danger" :disabled="!selectedIds.length" @click="handleBatchDelete">
|
|
||||||
批量删除
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<el-table :data="tableData" @selection-change="handleSelectionChange" v-loading="loading">
|
<n-card :bordered="false" class="proCard">
|
||||||
<el-table-column v-for="col in columns" v-bind="col" :key="col.prop || col.type">
|
<BasicTable
|
||||||
<template v-if="col.render" #default="scope">
|
:columns="columns"
|
||||||
<component :is="() => col.render(scope)" />
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="basicTableRef"
|
||||||
|
:actionColumn="actionColumn"
|
||||||
|
@update:checked-row-keys="onCheckedRow"
|
||||||
|
>
|
||||||
|
<template #tableTitle>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
|
||||||
|
新增
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="error"
|
||||||
|
:disabled="!rowKeys.length"
|
||||||
|
@click="handleDelete()"
|
||||||
|
v-perm="['sys:example:batchDelete']"
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</BasicTable>
|
||||||
<el-table-column label="操作" width="180" fixed="right">
|
</n-card>
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
|
||||||
<el-button type="primary" link @click="handleDetail(row)">详情</el-button>
|
|
||||||
<el-button type="danger" link @click="handleDelete(row.id)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="pageNo"
|
|
||||||
v-model:page-size="pageSize"
|
|
||||||
:total="total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@change="fetchData"
|
|
||||||
style="margin-top: 10px; justify-content: flex-end;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 编辑弹窗 -->
|
<!-- 编辑弹窗 -->
|
||||||
<EditDialog ref="editRef" @success="fetchData" />
|
<EditDialog
|
||||||
|
ref="editRef"
|
||||||
|
v-if="editVisible"
|
||||||
|
v-model:visible="editVisible"
|
||||||
|
:id="currentId"
|
||||||
|
@success="reloadTable('noRefresh')"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 详情弹窗 -->
|
<!-- 详情弹窗 -->
|
||||||
<DetailDialog v-model:visible="detailVisible" :id="detailId" />
|
<DetailDialog v-model:visible="detailVisible" :id="currentId" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h, nextTick, reactive, ref } from 'vue';
|
||||||
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
|
import { BasicTable, TableAction } from '@/components/Table';
|
||||||
|
import { BasicForm, useForm } from '@/components/Form';
|
||||||
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 } from './querySchemas';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
||||||
import EditDialog from './edit.vue';
|
import EditDialog from './edit.vue';
|
||||||
import DetailDialog from './detail.vue';
|
import DetailDialog from './detail.vue';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const dialog = useDialog();
|
||||||
|
const basicTableRef = ref();
|
||||||
const editRef = ref();
|
const editRef = ref();
|
||||||
const loading = ref(false);
|
const editVisible = ref(false);
|
||||||
const tableData = ref([]);
|
|
||||||
const pageNo = ref(1);
|
|
||||||
const pageSize = ref(20);
|
|
||||||
const total = ref(0);
|
|
||||||
const selectedIds = ref<number[]>([]);
|
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const detailId = ref(0);
|
const currentId = ref(0);
|
||||||
|
const rowKeys = ref([]);
|
||||||
|
|
||||||
// 加载数据
|
// 查询参数(与搜索表单字段一一对应)
|
||||||
const fetchData = async () => {
|
const formParams = reactive({ name: '', status: '' });
|
||||||
loading.value = true;
|
|
||||||
try {
|
// 操作列(render 返回 TableAction,auth 控制按钮权限)
|
||||||
const res = await getExamplePage({
|
const actionColumn = reactive({
|
||||||
pageNo: pageNo.value,
|
width: 280,
|
||||||
pageSize: pageSize.value,
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
render(record) {
|
||||||
|
return h(TableAction as any, {
|
||||||
|
actions: [
|
||||||
|
{ label: '编辑', type: 'info', onClick: handleEdit.bind(null, record), auth: ['sys:example:update'] },
|
||||||
|
{ label: '详情', type: 'info', onClick: handleDetail.bind(null, record), auth: ['sys:example:detail'] },
|
||||||
|
{ label: '删除', type: 'error', onClick: handleDelete.bind(null, record), auth: ['sys:example:delete'] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
tableData.value = res.records;
|
|
||||||
total.value = res.total;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 搜索
|
// 分页数据请求(BasicTable 内置分页,直接返回接口结果)
|
||||||
const handleSearch = (params) => {
|
const loadDataTable = async (params) => {
|
||||||
pageNo.value = 1;
|
rowKeys.value = [];
|
||||||
fetchData();
|
return await getExamplePage({ ...formParams, ...params });
|
||||||
};
|
|
||||||
|
|
||||||
// 重置
|
|
||||||
const handleReset = () => {
|
|
||||||
pageNo.value = 1;
|
|
||||||
fetchData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新增
|
|
||||||
const handleAdd = () => {
|
|
||||||
editRef.value.open();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑
|
|
||||||
const handleEdit = (row) => {
|
|
||||||
editRef.value.open(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 详情
|
|
||||||
const handleDetail = (row) => {
|
|
||||||
detailId.value = row.id;
|
|
||||||
detailVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
const handleDelete = async (id) => {
|
|
||||||
await ElMessageBox.confirm('确认删除该记录?', '提示', { type: 'warning' });
|
|
||||||
await exampleDelete(id);
|
|
||||||
ElMessage.success('删除成功');
|
|
||||||
fetchData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 批量删除
|
|
||||||
const handleBatchDelete = async () => {
|
|
||||||
await ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条记录?`, '提示', { type: 'warning' });
|
|
||||||
await exampleBatchDelete({ ids: selectedIds.value });
|
|
||||||
ElMessage.success('删除成功');
|
|
||||||
fetchData();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 多选变化
|
// 多选变化
|
||||||
const handleSelectionChange = (rows) => {
|
function onCheckedRow(keys) {
|
||||||
selectedIds.value = rows.map(row => row.id);
|
rowKeys.value = keys;
|
||||||
};
|
}
|
||||||
|
|
||||||
onMounted(() => fetchData());
|
// 刷新表格(不传参回第一页)
|
||||||
|
function reloadTable(noRefresh = '') {
|
||||||
|
basicTableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册搜索表单
|
||||||
|
const [register] = useForm({
|
||||||
|
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||||
|
labelWidth: 80,
|
||||||
|
schemas,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索 / 重置
|
||||||
|
function handleSubmit(values) {
|
||||||
|
for (const key in formParams) formParams[key] = '';
|
||||||
|
Object.assign(formParams, values);
|
||||||
|
reloadTable();
|
||||||
|
}
|
||||||
|
function handleReset() {
|
||||||
|
for (const key in formParams) formParams[key] = '';
|
||||||
|
reloadTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增 / 编辑 / 详情
|
||||||
|
const handleAdd = async () => {
|
||||||
|
currentId.value = 0;
|
||||||
|
editVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
editRef.value.openModal();
|
||||||
|
};
|
||||||
|
async function handleEdit(record) {
|
||||||
|
currentId.value = record.id;
|
||||||
|
editVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
editRef.value.openModal();
|
||||||
|
}
|
||||||
|
function handleDetail(record) {
|
||||||
|
currentId.value = record.id;
|
||||||
|
detailVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除(单条 / 批量共用,先弹确认框)
|
||||||
|
function handleDelete(record?) {
|
||||||
|
const ids = record ? [record.id] : rowKeys.value;
|
||||||
|
if (!ids.length) {
|
||||||
|
message.warning('请选择要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: record ? '确定删除该记录?' : `确定删除选中的 ${ids.length} 条记录?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
record ? await exampleDelete(record.id) : await exampleBatchDelete({ ids });
|
||||||
|
message.success('删除成功');
|
||||||
|
reloadTable();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## 步骤五:创建编辑弹窗
|
## 步骤五:创建编辑弹窗
|
||||||
|
|
||||||
|
使用项目封装的 `basicModal`(`useModal`)+ `n-form`:
|
||||||
|
|
||||||
```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">
|
<basicModal
|
||||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
|
@register="modalRegister"
|
||||||
<el-form-item label="名称" prop="name">
|
ref="modalRef"
|
||||||
<el-input v-model="formData.name" placeholder="请输入名称" />
|
@on-ok="handleSubmit"
|
||||||
</el-form-item>
|
@on-close="dialogClose"
|
||||||
<el-form-item label="类型" prop="type">
|
>
|
||||||
<el-select v-model="formData.type" placeholder="请选择类型">
|
<template #default>
|
||||||
<el-option v-for="item in typeOptions" :key="item.value" :label="item.name" :value="item.value" />
|
<n-form class="ls-form" ref="formRef" :model="formData" label-placement="left" label-width="85px">
|
||||||
</el-select>
|
<n-form-item
|
||||||
</el-form-item>
|
label="案例名称"
|
||||||
<el-form-item label="状态" prop="status">
|
path="name"
|
||||||
<el-radio-group v-model="formData.status">
|
:rule="{ required: true, message: '请输入案例名称', trigger: 'blur' }"
|
||||||
<el-radio :value="1">启用</el-radio>
|
>
|
||||||
<el-radio :value="0">禁用</el-radio>
|
<n-input v-model:value="formData.name" placeholder="请输入案例名称" clearable />
|
||||||
</el-radio-group>
|
</n-form-item>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="排序" prop="sort">
|
<n-form-item label="案例类型" path="type">
|
||||||
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
|
<n-select
|
||||||
</el-form-item>
|
v-model:value="formData.type"
|
||||||
</el-form>
|
placeholder="请选择案例类型"
|
||||||
<template #footer>
|
clearable
|
||||||
<el-button @click="visible = false">取消</el-button>
|
filterable
|
||||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
:options="typeOptions"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<n-form-item label="案例状态" path="status">
|
||||||
|
<n-radio-group v-model:value="formData.status">
|
||||||
|
<n-radio :value="1">在用</n-radio>
|
||||||
|
<n-radio :value="2">停用</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<n-form-item label="案例排序" path="sort">
|
||||||
|
<n-input-number v-model:value="formData.sort" :min="0" :max="99999" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</basicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import { exampleAdd, exampleUpdate, getExampleDetail } from '@/api/tool/example';
|
import { reactive, ref } from 'vue';
|
||||||
import { getDictItemList } from '@/api/common/index';
|
import { useMessage } from 'naive-ui';
|
||||||
import { ElMessage } from 'element-plus';
|
import { useModal } from '@/components/Modal';
|
||||||
|
import { useLockFn } from '@/utils/useLockFn';
|
||||||
|
import { getExampleDetail, exampleAdd, exampleUpdate } from '@/api/tool/example';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success', 'update:visible']);
|
||||||
|
const message = useMessage();
|
||||||
const visible = ref(false);
|
|
||||||
const submitting = ref(false);
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const typeOptions = ref([]);
|
|
||||||
|
|
||||||
const formData = ref({
|
const props = defineProps({
|
||||||
|
visible: { type: Boolean, required: true, default: false },
|
||||||
|
id: { type: Number, required: true, default: 0 }, // 0 表示新增
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeOptions = [
|
||||||
|
{ label: '类型1', value: 1 },
|
||||||
|
{ label: '类型2', value: 2 },
|
||||||
|
{ label: '类型3', value: 3 },
|
||||||
|
{ label: '类型4', value: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
type: '',
|
type: 0,
|
||||||
status: 1,
|
status: 1,
|
||||||
sort: 0,
|
sort: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = {
|
// 弹窗配置
|
||||||
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
const [modalRegister, { openModal, setSubLoading }] = useModal({
|
||||||
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
title: props.id ? '编辑案例演示' : '新增案例演示',
|
||||||
};
|
subBtuText: '确定',
|
||||||
|
width: 600,
|
||||||
|
});
|
||||||
|
|
||||||
// 打开弹窗
|
// 加载详情(编辑时回填)
|
||||||
const open = async (row?) => {
|
const setFormData = async () => {
|
||||||
visible.value = true;
|
|
||||||
formData.value = { id: 0, name: '', type: '', status: 1, sort: 0 };
|
|
||||||
|
|
||||||
// 加载字典选项
|
|
||||||
typeOptions.value = await getDictItemList('example_type');
|
|
||||||
|
|
||||||
// 编辑时加载详情
|
|
||||||
if (row?.id) {
|
|
||||||
const detail = await getExampleDetail(row.id);
|
|
||||||
Object.assign(formData.value, detail);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 提交
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
await formRef.value.validate();
|
|
||||||
submitting.value = true;
|
|
||||||
try {
|
try {
|
||||||
if (formData.value.id) {
|
const data = await getExampleDetail(props.id);
|
||||||
await exampleUpdate(formData.value);
|
for (const key in formData) {
|
||||||
ElMessage.success('修改成功');
|
if (data[key] != null) formData[key] = data[key];
|
||||||
} else {
|
|
||||||
await exampleAdd(formData.value);
|
|
||||||
ElMessage.success('新增成功');
|
|
||||||
}
|
}
|
||||||
visible.value = false;
|
} catch (error) {
|
||||||
emit('success');
|
message.error('获取数据失败');
|
||||||
} finally {
|
|
||||||
submitting.value = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ open });
|
// 提交:校验 → 新增/修改 → 提示 → 关闭并刷新
|
||||||
|
const handleSubmit = useLockFn(async () => {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
props.id ? await exampleUpdate(formData) : await exampleAdd(formData);
|
||||||
|
message.success('操作成功');
|
||||||
|
setSubLoading(false);
|
||||||
|
emit('update:visible', false);
|
||||||
|
emit('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogClose = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ openModal, setFormData });
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## 步骤六:创建详情弹窗
|
## 步骤六:创建详情弹窗
|
||||||
|
|
||||||
|
详情弹窗使用 `n-modal`(preset="card")+ `n-descriptions`:
|
||||||
|
|
||||||
```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">
|
<n-modal
|
||||||
<el-descriptions column="2" border>
|
v-model:show="props.visible"
|
||||||
<el-descriptions-item label="名称:">
|
preset="card"
|
||||||
|
title="案例演示详情"
|
||||||
|
:style="{ width: '750px' }"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="dialogClose"
|
||||||
|
>
|
||||||
|
<n-descriptions :column="2" bordered label-placement="left">
|
||||||
|
<n-descriptions-item label="案例名称:">
|
||||||
{{ formData.name || '-' }}
|
{{ formData.name || '-' }}
|
||||||
</el-descriptions-item>
|
</n-descriptions-item>
|
||||||
<el-descriptions-item label="类型:">
|
<n-descriptions-item label="案例图片:">
|
||||||
{{ formData.typeText || '-' }}
|
<n-image
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="状态:">
|
|
||||||
<el-tag :type="formData.status === 1 ? 'success' : 'danger'">
|
|
||||||
{{ formData.statusText || '-' }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="排序:">
|
|
||||||
{{ formData.sort ?? '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="头像:">
|
|
||||||
<el-image
|
|
||||||
v-if="formData.avatar"
|
v-if="formData.avatar"
|
||||||
:src="formData.avatar"
|
:src="formData.avatar"
|
||||||
:preview-src-list="[formData.avatar]"
|
:preview-src="formData.avatar"
|
||||||
fit="cover"
|
width="80"
|
||||||
style="width: 80px; height: 80px"
|
height="80"
|
||||||
|
object-fit="cover"
|
||||||
/>
|
/>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</el-descriptions-item>
|
</n-descriptions-item>
|
||||||
<el-descriptions-item label="创建人:">
|
<n-descriptions-item label="案例状态:">
|
||||||
{{ formData.createUser || '-' }}
|
<n-tag :type="formData.status === 1 ? 'success' : 'error'" bordered>
|
||||||
</el-descriptions-item>
|
{{ formData.status === 1 ? '在用' : '停用' }}
|
||||||
<el-descriptions-item label="创建时间:">
|
</n-tag>
|
||||||
{{ formData.createTime || '-' }}
|
</n-descriptions-item>
|
||||||
</el-descriptions-item>
|
<n-descriptions-item label="案例排序:">
|
||||||
</el-descriptions>
|
{{ formData.sort || '-' }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="emit('update:visible', false)">关闭</el-button>
|
<div style="text-align: right">
|
||||||
|
<n-button @click="dialogClose">关闭</n-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { reactive, watch } from 'vue';
|
||||||
|
import { NImage } from 'naive-ui';
|
||||||
import { getExampleDetail } from '@/api/tool/example';
|
import { getExampleDetail } from '@/api/tool/example';
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible']);
|
const emit = defineEmits(['update:visible']);
|
||||||
@@ -498,15 +551,12 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
type: '',
|
|
||||||
typeText: '',
|
|
||||||
status: 0,
|
|
||||||
statusText: '',
|
|
||||||
sort: 0,
|
|
||||||
avatar: '',
|
avatar: '',
|
||||||
createUser: '',
|
type: 0,
|
||||||
createTime: '',
|
status: 0,
|
||||||
|
sort: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载详情
|
// 加载详情
|
||||||
@@ -514,21 +564,25 @@ const loadDetail = async () => {
|
|||||||
try {
|
try {
|
||||||
const data = await getExampleDetail(props.id);
|
const data = await getExampleDetail(props.id);
|
||||||
for (const key in formData) {
|
for (const key in formData) {
|
||||||
if (data[key] != null) {
|
if (data[key] != null) formData[key] = data[key];
|
||||||
formData[key] = data[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取详情失败', error);
|
console.error('获取详情失败', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听弹窗显示
|
const dialogClose = () => {
|
||||||
watch(() => props.visible, (newVal) => {
|
emit('update:visible', false);
|
||||||
if (newVal && props.id) {
|
};
|
||||||
loadDetail();
|
|
||||||
}
|
// 监听弹窗显示,加载数据
|
||||||
}, { immediate: true });
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal && props.id) loadDetail();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
# 9.1 前端工程概述
|
# 9.1 前端工程概述
|
||||||
|
|
||||||
:::tip 概述
|
:::tip 概述
|
||||||
前端基于 Vue 3 + TypeScript + Vite + ElementPlus 构建,采用 Pinia 状态管理,支持动态菜单和权限控制。通过 Axios 封装统一处理请求/响应/错误,通过 v-perm 指令实现按钮级权限控制。
|
前端基于 Vue 3 + TypeScript + Vite + NaiveUI 构建,采用 Pinia 状态管理,支持动态菜单和权限控制。通过 Axios 封装统一处理请求/响应/错误,通过 v-perm 指令实现按钮级权限控制。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
| 类别 | 技术 | 版本 | 说明 |
|
| 类别 | 技术 | 版本 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| 框架 | Vue | 3.4+ | 核心 UI 框架 |
|
| 框架 | Vue | 3.5+(实际 ^3.5.1) | 核心 UI 框架 |
|
||||||
| 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 |
|
| 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 |
|
||||||
| UI 库 | ElementPlus | 2.7+ | 组件库 |
|
| UI 库 | NaiveUI | 2.40+(实际 ^2.40.0) | 组件库 |
|
||||||
| 构建 | Vite | 3.2+(实际 ^3.2.10) | 开发服务器与构建 |
|
| 构建 | Vite | 5.4+(实际 ^5.4.3) | 开发服务器与构建 |
|
||||||
| 状态 | Pinia | 2.1+ | 全局状态管理 |
|
| 状态 | Pinia | 2.2+(实际 ^2.2.2) | 全局状态管理 |
|
||||||
| 路由 | Vue Router | 4.3+ | 路由管理 |
|
| 路由 | Vue Router | 4.4+(实际 ^4.4.3) | 路由管理 |
|
||||||
| HTTP | Axios | 0.27+(实际 ^0.27.2) | HTTP 请求(注:0.27.x 版本较老) |
|
| HTTP | Axios | 1.7+(实际 ^1.7.7) | HTTP 请求 |
|
||||||
| 富文本 | TinyMce | 5.10+ | 富文本编辑器 |
|
| 富文本 | TinyMce / Quill | 5.10+ | 富文本编辑器 |
|
||||||
| 图表 | ECharts | 5.5+ | 数据可视化 |
|
| 图表 | ECharts | 5.5+ | 数据可视化 |
|
||||||
| CSS | TailwindCSS | 3.4+ | 原子化 CSS |
|
| CSS | TailwindCSS + Less | 3.4+ | 原子化 CSS 与预处理 |
|
||||||
|
|
||||||
## 核心特性
|
## 核心特性
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
`v-perm` / `v-perms` 指令控制按钮级权限:
|
`v-perm` / `v-perms` 指令控制按钮级权限:
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<el-button v-perm="'sys:user:add'">新增</el-button>
|
<n-button v-perm="'sys:user:add'">新增</n-button>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Token 管理
|
### Token 管理
|
||||||
|
|||||||
@@ -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>
|
<n-button v-perm="'sys:user:add'" type="primary">新增</n-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>
|
<n-button v-perms="['sys:user:add', 'sys:user:update']">操作</n-button>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 9.14 样式与主题定制
|
# 9.14 样式与主题定制
|
||||||
|
|
||||||
:::tip 概述
|
:::tip 概述
|
||||||
前端使用 TailwindCSS + SCSS 实现样式管理,支持主题色切换和暗色模式。
|
前端使用 TailwindCSS + Less 实现样式管理,组件主题通过 NaiveUI 的 `NConfigProvider`(`theme` / `themeOverrides`)实现主题色切换和暗色模式。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
@@ -9,38 +9,45 @@
|
|||||||
| 技术 | 用途 |
|
| 技术 | 用途 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| TailwindCSS | 原子化 CSS 框架 |
|
| TailwindCSS | 原子化 CSS 框架 |
|
||||||
| SCSS | 全局样式变量和主题 |
|
| Less | 全局样式变量和公共样式 |
|
||||||
| ElementPlus 主题变量 | 组件样式覆盖 |
|
| NaiveUI 主题机制 | NConfigProvider 的 theme / themeOverrides 组件主题覆盖 |
|
||||||
|
|
||||||
## 全局样式文件
|
## 全局样式文件
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ui/src/styles/
|
ui/src/styles/
|
||||||
├── index.scss # 全局样式入口(@import 所有子模块)
|
├── index.less # 全局样式入口(@import 子模块)
|
||||||
├── var.scss # SCSS 变量定义(颜色、尺寸等)
|
├── var.less # Less 变量定义(颜色、尺寸等)
|
||||||
├── common.scss # 公共样式类
|
├── common.less # 公共样式类
|
||||||
├── tailwind.css # TailwindCSS 入口
|
├── tailwind.css # TailwindCSS 入口
|
||||||
├── tailwindbase.css # TailwindCSS 基础重置
|
└── transition/ # 过渡动画
|
||||||
├── theme/
|
├── index.less
|
||||||
│ ├── light.scss # 浅色主题(含 ElementPlus 组件覆盖)
|
├── base.less / fade.less / scale.less
|
||||||
│ └── dark.scss # 深色主题
|
└── slide.less / zoom.less / scroll.less
|
||||||
└── transition/
|
|
||||||
└── index.scss # 过渡动画
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## SCSS 变量
|
## Less 变量
|
||||||
|
|
||||||
```scss
|
```less
|
||||||
/* ui/src/styles/var.scss */
|
/* ui/src/styles/var.less */
|
||||||
$primaryColor: #165DFF;
|
@primaryColor: #2d8cf0;
|
||||||
$primaryColorHover: #57a3f3;
|
@primaryColorHover: #57a3f3;
|
||||||
$header-height: 64px;
|
@header-height: 64px;
|
||||||
$footer-height: 70px;
|
@footer-height: 70px;
|
||||||
|
```
|
||||||
|
|
||||||
|
全局样式入口:
|
||||||
|
|
||||||
|
```less
|
||||||
|
/* ui/src/styles/index.less */
|
||||||
|
@import './transition/index.less';
|
||||||
|
@import './var.less';
|
||||||
|
@import './common.less';
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主题色切换
|
## 主题色切换
|
||||||
|
|
||||||
通过 `designSettingStore` 管理主题:
|
通过 `designSettingStore` 管理主题,`App.vue` 将主题注入 `NConfigProvider`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||||
@@ -48,49 +55,91 @@ import { useDesignSettingStore } from '@/store/modules/designSetting';
|
|||||||
const designStore = useDesignSettingStore();
|
const designStore = useDesignSettingStore();
|
||||||
|
|
||||||
// 切换暗色模式
|
// 切换暗色模式
|
||||||
designStore.setDarkMode(true);
|
designStore.setDarkTheme(true);
|
||||||
|
|
||||||
// 获取当前主题色
|
// 获取当前主题色(可选主题色列表见 designStore.appThemeList)
|
||||||
const primaryColor = designStore.appTheme;
|
const primaryColor = designStore.appTheme;
|
||||||
```
|
```
|
||||||
|
|
||||||
## ElementPlus 主题覆盖
|
主题色配置来源:
|
||||||
|
|
||||||
ElementPlus 组件样式覆盖定义在主题文件中,通过 `[data-theme]` 选择器区分深浅色模式:
|
```typescript
|
||||||
|
// ui/src/settings/designSetting.ts
|
||||||
|
export const appThemeList: string[] = ['#2d8cf0', '#0960bd', '#0084f4', /* ... */];
|
||||||
|
|
||||||
```scss
|
const setting = {
|
||||||
/* ui/src/styles/theme/light.scss */
|
darkTheme: false, // 深色主题开关
|
||||||
[data-theme='light'] {
|
appTheme: '#2d8cf0', // 系统主题色
|
||||||
--el-border-color-base: #e4e7ed;
|
appThemeList, // 内置主题色列表
|
||||||
|
};
|
||||||
.el-table {
|
|
||||||
--el-table-header-bg-color: #f5f7fa;
|
|
||||||
--el-table-header-text-color: rgb(31 34 37);
|
|
||||||
}
|
|
||||||
// ...更多组件覆盖
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## NaiveUI 主题覆盖
|
||||||
|
|
||||||
|
`App.vue` 根主题注入 `NConfigProvider`,深色模式传 `darkTheme`,主题色通过 `themeOverrides` 覆盖:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- ui/src/App.vue -->
|
||||||
|
<template>
|
||||||
|
<NConfigProvider
|
||||||
|
:date-locale="dateZhCN"
|
||||||
|
:locale="zhCN"
|
||||||
|
:theme="getDarkTheme"
|
||||||
|
:theme-overrides="getThemeOverrides"
|
||||||
|
>
|
||||||
|
<!-- 应用内容 -->
|
||||||
|
</NConfigProvider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { darkTheme, dateZhCN, zhCN } from 'naive-ui';
|
||||||
|
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||||
|
import { lighten } from '@/utils';
|
||||||
|
|
||||||
|
const designStore = useDesignSettingStore();
|
||||||
|
|
||||||
|
// 主题色覆盖(GlobalThemeOverrides)
|
||||||
|
const getThemeOverrides = computed(() => {
|
||||||
|
const appTheme = designStore.appTheme;
|
||||||
|
const lightenStr = lighten(designStore.appTheme, 6);
|
||||||
|
return {
|
||||||
|
common: {
|
||||||
|
primaryColor: appTheme,
|
||||||
|
primaryColorSuppl: appTheme,
|
||||||
|
primaryColorHover: lightenStr,
|
||||||
|
colorLoading: appTheme,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 深色模式:传入 naive-ui 的 darkTheme,否则 undefined(浅色)
|
||||||
|
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip 说明
|
||||||
|
NaiveUI 组件样式由 CSS 变量(`--n-*`)驱动,`themeOverrides` 会自动生成对应的 CSS 变量并应用到所有组件。
|
||||||
|
:::
|
||||||
|
|
||||||
## TailwindCSS 配置
|
## TailwindCSS 配置
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ui/tailwind.config.js
|
// ui/tailwind.config.js
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./src/**/*.{vue,ts,tsx}'],
|
content: ['./public/**/*.html', './index.html', './src/**/*.{js,jsx,ts,tsx,vue}'],
|
||||||
|
important: true,
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {},
|
||||||
colors: {
|
|
||||||
primary: 'var(--primary-color)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
plugins: [],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## 自定义全局样式
|
## 自定义全局样式
|
||||||
|
|
||||||
```scss
|
```less
|
||||||
/* ui/src/styles/index.scss */
|
/* ui/src/styles/common.less */
|
||||||
|
|
||||||
// 页面容器
|
// 页面容器
|
||||||
.page-container {
|
.page-container {
|
||||||
@@ -107,3 +156,17 @@ module.exports = {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 局部样式覆盖
|
||||||
|
|
||||||
|
组件内如需覆盖 NaiveUI 样式,可使用 NaiveUI 提供的 `--n-*` CSS 变量或 TailwindCSS:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<style scoped>
|
||||||
|
/* 覆盖 n-card 内边距 */
|
||||||
|
.custom-card {
|
||||||
|
--n-padding-top: 8px;
|
||||||
|
--n-padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
| `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 | 表格列配置(含图片 NAvatar、选项 NTag、链接等渲染) |
|
||||||
| `ui/src/views/{app}/{name}/querySchemas.ts` | querySchemas.ts.tpl | 查询表单配置(按字段类型自动选 Input/Select/InputNumber/DatePicker) |
|
| `ui/src/views/{app}/{name}/querySchemas.ts` | querySchemas.ts.tpl | 查询表单配置(按字段类型自动选 NInput/NSelect/NInputNumber/NDatePicker) |
|
||||||
|
|
||||||
### 前端文件(ui2/ 树形模板 — 4 个文件)
|
### 前端文件(ui2/ 树形模板 — 4 个文件)
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public function getTableList(): array
|
|||||||
| 类型 | VARCHAR |
|
| 类型 | VARCHAR |
|
||||||
| 匹配 | 注释或字段名匹配 `/图片\|封面\|头像\|logo\|icon\|image\|avatar\|photo/i` |
|
| 匹配 | 注释或字段名匹配 `/图片\|封面\|头像\|logo\|icon\|image\|avatar\|photo/i` |
|
||||||
|
|
||||||
识别后:`is_image = true`,归入 `file_fields`,前端用 ElAvatar 渲染。
|
识别后:`is_image = true`,归入 `file_fields`,前端用 NAvatar 渲染。
|
||||||
|
|
||||||
### 富文本字段
|
### 富文本字段
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ public function getTableList(): array
|
|||||||
|
|
||||||
标签颜色映射:
|
标签颜色映射:
|
||||||
|
|
||||||
| 选项值 | 标签颜色 | Element Plus type |
|
| 选项值 | 标签颜色 | 标签 type |
|
||||||
|--------|---------|-------------------|
|
|--------|---------|-------------------|
|
||||||
| 1 | 绿色 | `success` |
|
| 1 | 绿色 | `success` |
|
||||||
| 2 | 红色 | `danger` |
|
| 2 | 红色 | `danger` |
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ $field = [
|
|||||||
'default' => 0, // 默认值
|
'default' => 0, // 默认值
|
||||||
'is_image' => false, // 是否为图片字段
|
'is_image' => false, // 是否为图片字段
|
||||||
'is_rich_text' => false, // 是否为富文本字段
|
'is_rich_text' => false, // 是否为富文本字段
|
||||||
'choices' => [ // 选项列表(有值时可生成 ElTag 渲染)
|
'choices' => [ // 选项列表(有值时可生成 NTag 渲染)
|
||||||
['value' => 0, 'label' => '正常', 'type' => 'success'],
|
['value' => 0, 'label' => '正常', 'type' => 'success'],
|
||||||
['value' => 1, 'label' => '禁用', 'type' => 'danger'],
|
['value' => 1, 'label' => '禁用', 'type' => 'danger'],
|
||||||
],
|
],
|
||||||
@@ -159,18 +159,18 @@ class <?php echo $model_class_name ?>Logic extends BaseLogic
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { ElAvatar, ElTag } from 'element-plus';
|
import { NTag, NAvatar } from 'naive-ui';
|
||||||
|
|
||||||
export const columns = [
|
export const columns = [
|
||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
|
{ title: 'ID', key: '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'] ?>',
|
key: '<?php echo $field['camel_name'] ?>',
|
||||||
<?php if ($field['is_image']): ?>
|
<?php if ($field['is_image']): ?>
|
||||||
render(record) {
|
render(record) {
|
||||||
return h(ElAvatar, { size: 48, src: record.row.<?php echo $field['camel_name'] ?> });
|
return h(NAvatar, { size: 48, src: record.<?php echo $field['camel_name'] ?>, round: false });
|
||||||
},
|
},
|
||||||
<?php elseif ($field['choices']): ?>
|
<?php elseif ($field['choices']): ?>
|
||||||
render(record) {
|
render(record) {
|
||||||
@@ -179,8 +179,8 @@ export const columns = [
|
|||||||
<?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', type: '<?php echo $choice['type'] ?>' },
|
<?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', type: '<?php echo $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(NTag, { bordered: false, type: s?.type || 'info' }, { default: () => s?.text });
|
||||||
},
|
},
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
},
|
},
|
||||||
@@ -221,8 +221,8 @@ 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 导入,含 NTag/NAvatar 渲染 |
|
||||||
| `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 Select/Input/DatePicker 组件 |
|
| `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 NSelect/NInput/NDatePicker 组件 |
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
|
|||||||
@@ -165,93 +165,168 @@ export function exampleExport(params) {
|
|||||||
| `querySchemas.ts` | 搜索表单字段定义 |
|
| `querySchemas.ts` | 搜索表单字段定义 |
|
||||||
|
|
||||||
:::tip 关于以下示例
|
:::tip 关于以下示例
|
||||||
以下代码使用原生 `el-table` / `el-form` 简化展示核心逻辑。实际项目中应使用封装好的 `BasicTable` + `useForm` 等组合式组件,详见[第 9 章组件文档](/zh/frontend/overview.html)。
|
以下代码来自项目实际的 `example` 演示模块,使用封装好的 `BasicTable` + `BasicForm` 组合式组件,详见[第 9 章组件文档](/zh/frontend/overview.html)。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
`index.vue`(列表页)示例:
|
`index.vue`(列表页)示例:
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<!-- ui/src/views/tool/example/index.vue -->
|
<!-- ui/src/views/tool/example/index.vue(节选,来自项目实际代码) -->
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 搜索表单 -->
|
<!-- 搜索表单卡片 -->
|
||||||
<el-form :model="searchForm" inline>
|
<n-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||||
<el-form-item label="名称">
|
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||||
<el-input v-model="searchForm.name" placeholder="请输入名称" />
|
</n-card>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="状态">
|
|
||||||
<el-select v-model="searchForm.status" placeholder="请选择" clearable>
|
|
||||||
<el-option label="启用" :value="1" />
|
|
||||||
<el-option label="禁用" :value="0" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
||||||
<el-button @click="handleReset">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 数据表格卡片 -->
|
||||||
<el-button type="primary" @click="handleAdd">新增</el-button>
|
<n-card :bordered="false" class="proCard">
|
||||||
<el-button type="danger" :disabled="!selectedIds.length" @click="handleBatchDelete">
|
<BasicTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="basicTableRef"
|
||||||
|
:actionColumn="actionColumn"
|
||||||
|
@update:checked-row-keys="onCheckedRow"
|
||||||
|
>
|
||||||
|
<!-- 表格顶部工具栏 -->
|
||||||
|
<template #tableTitle>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
|
||||||
|
添加案例演示
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="error"
|
||||||
|
@click="handleDelete()"
|
||||||
|
:disabled="!rowKeys.length"
|
||||||
|
v-perm="['sys:example:batchDelete']"
|
||||||
|
>
|
||||||
批量删除
|
批量删除
|
||||||
</el-button>
|
</n-button>
|
||||||
|
</n-space>
|
||||||
<!-- 数据表格 -->
|
|
||||||
<el-table :data="tableData" @selection-change="handleSelectionChange">
|
|
||||||
<el-table-column type="selection" width="55" />
|
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
|
||||||
<el-table-column prop="name" label="名称" />
|
|
||||||
<el-table-column prop="typeText" label="类型" width="100" />
|
|
||||||
<el-table-column prop="statusText" label="状态" width="100" />
|
|
||||||
<el-table-column prop="sort" label="排序" width="80" />
|
|
||||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
|
||||||
<el-table-column label="操作" width="200">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
|
|
||||||
<el-button size="small" type="danger" @click="handleDelete(row.id)">删除</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</BasicTable>
|
||||||
</el-table>
|
</n-card>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 新增/编辑弹窗 -->
|
||||||
<el-pagination
|
<editDialog
|
||||||
v-model:current-page="pageNo"
|
ref="createModalRef"
|
||||||
v-model:page-size="pageSize"
|
v-if="editVisible"
|
||||||
:total="total"
|
v-model:visible="editVisible"
|
||||||
@current-change="fetchData"
|
@success="reloadTable('noRefresh')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { h, nextTick, reactive, ref } from 'vue';
|
||||||
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
|
import { BasicTable, TableAction } from '@/components/Table';
|
||||||
|
import { BasicForm, useForm } from '@/components/Form';
|
||||||
|
import {
|
||||||
|
getExamplePage,
|
||||||
|
exampleDelete,
|
||||||
|
exampleBatchDelete,
|
||||||
|
} from '@/api/tool/example';
|
||||||
|
import { columns } from './columns';
|
||||||
|
import { schemas } from './querySchemas';
|
||||||
|
import editDialog from './edit.vue';
|
||||||
|
|
||||||
const searchForm = ref({ name: '', status: '' });
|
const message = useMessage();
|
||||||
const tableData = ref([]);
|
const dialog = useDialog();
|
||||||
const pageNo = ref(1);
|
const basicTableRef = ref();
|
||||||
const pageSize = ref(20);
|
const createModalRef = ref();
|
||||||
const total = ref(0);
|
const editVisible = ref(false);
|
||||||
const selectedIds = ref([]);
|
const rowKeys = ref([]);
|
||||||
|
|
||||||
const fetchData = async () => {
|
// 查询参数(与搜索表单字段一一对应)
|
||||||
const res = await getExamplePage({
|
const formParams = reactive({ name: '', type: '', status: '' });
|
||||||
pageNo: pageNo.value,
|
|
||||||
pageSize: pageSize.value,
|
// 操作列(固定右侧,auth 控制按钮权限)
|
||||||
...searchForm.value,
|
const actionColumn = reactive({
|
||||||
|
width: 280,
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
render(record) {
|
||||||
|
return h(TableAction as any, {
|
||||||
|
actions: [
|
||||||
|
{ label: '编辑', type: 'info', onClick: handleEdit.bind(null, record), auth: ['sys:example:update'] },
|
||||||
|
{ label: '删除', type: 'error', onClick: handleDelete.bind(null, record), auth: ['sys:example:delete'] },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
tableData.value = res.records;
|
},
|
||||||
total.value = res.total;
|
});
|
||||||
|
|
||||||
|
// 分页数据请求(BasicTable 内置分页)
|
||||||
|
const loadDataTable = async (params) => {
|
||||||
|
rowKeys.value = [];
|
||||||
|
return await getExamplePage({ ...formParams, ...params });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => { pageNo.value = 1; fetchData(); };
|
// 数据行选中
|
||||||
const handleReset = () => { searchForm.value = { name: '', status: '' }; handleSearch(); };
|
function onCheckedRow(keys) {
|
||||||
const handleDelete = async (id: number) => { await exampleDelete(id); fetchData(); };
|
rowKeys.value = keys;
|
||||||
const handleBatchDelete = async () => { await exampleBatchDelete({ ids: selectedIds.value }); fetchData(); };
|
}
|
||||||
const handleSelectionChange = (rows: any[]) => { selectedIds.value = rows.map((r) => r.id); };
|
|
||||||
|
|
||||||
onMounted(() => fetchData());
|
// 刷新表格
|
||||||
|
function reloadTable(noRefresh = '') {
|
||||||
|
basicTableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册搜索表单
|
||||||
|
const [register] = useForm({
|
||||||
|
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||||
|
labelWidth: 80,
|
||||||
|
schemas,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提交搜索(条件写回后回第一页)
|
||||||
|
function handleSubmit(values) {
|
||||||
|
for (const key in formParams) formParams[key] = '';
|
||||||
|
Object.assign(formParams, values);
|
||||||
|
reloadTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
function handleReset() {
|
||||||
|
for (const key in formParams) formParams[key] = '';
|
||||||
|
reloadTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = async () => {
|
||||||
|
editVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
createModalRef.value.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
async function handleEdit(record) {
|
||||||
|
editVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
createModalRef.value.openModal(record.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除(单条 / 批量,先弹确认框)
|
||||||
|
function handleDelete(record?) {
|
||||||
|
const ids = record ? [record.id] : rowKeys.value;
|
||||||
|
if (!ids.length) {
|
||||||
|
message.warning('请选择要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: record ? '确定删除该记录?' : `确定删除选中的 ${ids.length} 条记录?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
record ? await exampleDelete(record.id) : await exampleBatchDelete({ ids });
|
||||||
|
message.success('删除成功');
|
||||||
|
reloadTable();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
## 软件说明
|
## 软件说明
|
||||||
|
|
||||||
基于 ThinkPHP 6.1、PHP 8.2、Vue3、TypeScript、Vite、ElementPlus 等技术栈构建的企业级前后端分离后台管理系统,支持 MySQL、PostgreSQL、SQL Server、Oracle、SQLite 等数据库。
|
基于 ThinkPHP 6.1、PHP 8.2、Vue3、TypeScript、Vite、NaiveUI 等技术栈构建的企业级前后端分离后台管理系统,支持 MySQL、PostgreSQL、SQL Server、Oracle、SQLite 等数据库。
|
||||||
|
|
||||||
后端采用 PHP 语言,基于 ThinkPHP 6.1 框架、ThinkPHP ORM 数据库操作、PHP 8 Attribute 注解等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 ElementPlus 框架,遵循 Vue 前后端分离主流设计思想。
|
后端采用 PHP 语言,基于 ThinkPHP 6.1 框架、ThinkPHP ORM 数据库操作、PHP 8 Attribute 注解等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 NaiveUI 框架,遵循 Vue 前后端分离主流设计思想。
|
||||||
|
|
||||||
系统采用完全前后端分离架构,前端通过 API 调用后端服务实现数据交互。内置完善的 RBAC 权限体系,权限颗粒度精细至按钮级别;支持多租户数据隔离和数据权限控制。系统预置了用户、角色、菜单、部门、岗位、职级、日志、字典、配置、参数、定时任务、代码生成器等基础功能模块,开箱即用。
|
系统采用完全前后端分离架构,前端通过 API 调用后端服务实现数据交互。内置完善的 RBAC 权限体系,权限颗粒度精细至按钮级别;支持多租户数据隔离和数据权限控制。系统预置了用户、角色、菜单、部门、岗位、职级、日志、字典、配置、参数、定时任务、代码生成器等基础功能模块,开箱即用。
|
||||||
|
|
||||||
@@ -54,12 +54,12 @@
|
|||||||
|
|
||||||
## 软件出处
|
## 软件出处
|
||||||
|
|
||||||
+ 软件名称:RXThinkCMF前后端分离ThinkPHP6+ElementPlus版本
|
+ 软件名称:RXThinkCMF前后端分离ThinkPHP6+NaiveUI版本
|
||||||
+ 软件作者:@小蚂蚁云团队
|
+ 软件作者:@小蚂蚁云团队
|
||||||
+ 软件协议:Apache-2.0
|
+ 软件协议:Apache-2.0
|
||||||
+ 官网网址:[https://www.rxthink.cn](https://www.rxthink.cn)
|
+ 官网网址:[https://www.rxthink.cn](https://www.rxthink.cn)
|
||||||
+ 文档网址:[http://docs.thinkphp6.elevue.rxthink.cn](http://docs.thinkphp6.elevue.rxthink.cn)
|
+ 文档网址:[http://docs.thinkphp6.naivevue.rxthink.cn](http://docs.thinkphp6.naivevue.rxthink.cn)
|
||||||
+ 演示地址:[http://manage.thinkphp6.elevue.rxthink.cn](http://manage.thinkphp6.elevue.rxthink.cn)
|
+ 演示地址:[http://manage.thinkphp6.naivevue.rxthink.cn](http://manage.thinkphp6.naivevue.rxthink.cn)
|
||||||
|
|
||||||
## 软件版本
|
## 软件版本
|
||||||
|
|
||||||
@@ -138,6 +138,10 @@
|
|||||||
<td><img src="/demo/27.png"/></td>
|
<td><img src="/demo/27.png"/></td>
|
||||||
<td><img src="/demo/28.png"/></td>
|
<td><img src="/demo/28.png"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="/demo/29.png"/></td>
|
||||||
|
<td><img src="/demo/30.png"/></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ think job:run
|
|||||||
11. 起始于:
|
11. 起始于:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
D:\xampp\htdocs\v3\thinkphp6
|
D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue
|
||||||
```
|
```
|
||||||
|
|
||||||
12. 勾选"不管用户是否登录都要运行"
|
12. 勾选"不管用户是否登录都要运行"
|
||||||
@@ -252,7 +252,7 @@ D:\xampp\htdocs\v3\thinkphp6
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 手动运行测试
|
# 手动运行测试
|
||||||
cd D:\xampp\htdocs\v3\thinkphp6
|
cd D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue
|
||||||
C:\xampp\php\php.exe think job:run
|
C:\xampp\php\php.exe think job:run
|
||||||
|
|
||||||
# 在任务计划程序中右键任务 → 运行
|
# 在任务计划程序中右键任务 → 运行
|
||||||
@@ -274,7 +274,7 @@ schtasks /create /tn "RXThinkCMF_Job" /tr "C:\xampp\php\php.exe think job:run" /
|
|||||||
# /f 强制覆盖同名任务
|
# /f 强制覆盖同名任务
|
||||||
|
|
||||||
# 创建任务(指定工作目录)
|
# 创建任务(指定工作目录)
|
||||||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp\htdocs\v3\thinkphp6 && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
||||||
|
|
||||||
# 查询任务
|
# 查询任务
|
||||||
schtasks /query /tn "RXThinkCMF_Job"
|
schtasks /query /tn "RXThinkCMF_Job"
|
||||||
@@ -301,7 +301,7 @@ schtasks /query /fo table
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 带日志输出的完整命令
|
# 带日志输出的完整命令
|
||||||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp\htdocs\v3\thinkphp6 && C:\xampp\php\php.exe think job:run >> runtime\job.log 2>&1" /sc minute /mo 1 /f
|
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue && C:\xampp\php\php.exe think job:run >> runtime\job.log 2>&1" /sc minute /mo 1 /f
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -632,7 +632,7 @@ crontab -e
|
|||||||
# 添加:* * * * * cd /www/api && php think job:run >> /dev/null 2>&1
|
# 添加:* * * * * cd /www/api && php think job:run >> /dev/null 2>&1
|
||||||
|
|
||||||
# 方式二:Windows 计划任务
|
# 方式二:Windows 计划任务
|
||||||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp\htdocs\v3\thinkphp6 && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
||||||
|
|
||||||
# 方式三:Daemon 守护进程
|
# 方式三:Daemon 守护进程
|
||||||
php think job:daemon
|
php think job:daemon
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ feat(generator): 支持树形表格模板生成
|
|||||||
|
|
||||||
- 新增 ui2/ 树形模板目录
|
- 新增 ui2/ 树形模板目录
|
||||||
- 自动识别 parent_id/pid 字段
|
- 自动识别 parent_id/pid 字段
|
||||||
- 支持 el-tree-select 组件
|
- 支持 n-tree-select 组件
|
||||||
|
|
||||||
Closes #123
|
Closes #123
|
||||||
```
|
```
|
||||||
@@ -176,12 +176,6 @@ Closes #xxx
|
|||||||
- TypeScript/Vue:遵循 ESLint + Prettier 配置
|
- TypeScript/Vue:遵循 ESLint + Prettier 配置
|
||||||
- 详见 [前端编码规范](/zh/standards/frontend-code.html) 和 [后端编码规范](/zh/standards/backend-code.html)
|
- 详见 [前端编码规范](/zh/standards/frontend-code.html) 和 [后端编码规范](/zh/standards/backend-code.html)
|
||||||
|
|
||||||
## 文档贡献
|
|
||||||
|
|
||||||
- 文档位于 `wiki/zh/` 目录,使用 VitePress + Markdown
|
|
||||||
- 新增功能需同步更新文档
|
|
||||||
- 文档遵循 [文档规范](/zh/standards/backend-code.html)
|
|
||||||
|
|
||||||
## 行为准则
|
## 行为准则
|
||||||
|
|
||||||
- 尊重每一位贡献者
|
- 尊重每一位贡献者
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进入项目目录(以实际解压路径为准)
|
# 进入项目目录(以实际解压路径为准)
|
||||||
cd thinkphp6
|
cd RXThinkCMF_ThinkPhp6_NaiveVue
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip 温馨提示
|
:::tip 温馨提示
|
||||||
@@ -64,7 +64,7 @@ APP_DEBUG = true
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql ; 数据库类型(mysql / pgsql / sqlsrv / oracle / sqlite)
|
TYPE = mysql ; 数据库类型(mysql / pgsql / sqlsrv / oracle / sqlite)
|
||||||
HOSTNAME = 127.0.0.1 ; 数据库服务器地址
|
HOSTNAME = 127.0.0.1 ; 数据库服务器地址
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue ; 数据库名称
|
DATABASE = rxthinkcmf.thinkphp6.naivevue ; 数据库名称
|
||||||
USERNAME = root ; 数据库用户名
|
USERNAME = root ; 数据库用户名
|
||||||
PASSWORD = root ; 数据库密码
|
PASSWORD = root ; 数据库密码
|
||||||
HOSTPORT = 3306 ; 数据库端口
|
HOSTPORT = 3306 ; 数据库端口
|
||||||
@@ -76,8 +76,8 @@ SECRET = rxthinkcmf_jwt_secret_key_2024
|
|||||||
|
|
||||||
; 文件上传配置
|
; 文件上传配置
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf ; 上传文件存储目录(绝对路径)
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads ; 上传文件存储目录(绝对路径)
|
||||||
DOMAIN_URL = http://file.thinkphp6.elevue ; 文件访问域名
|
DOMAIN_URL = http://file.thinkphp6.naivevue ; 文件访问域名
|
||||||
```
|
```
|
||||||
|
|
||||||
:::danger 生产环境务必修改
|
:::danger 生产环境务必修改
|
||||||
|
|||||||
@@ -9,15 +9,15 @@
|
|||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/
|
├── mysql/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # MySQL 版本
|
||||||
├── postgresql/
|
├── postgresql/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/
|
├── sqlserver/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # SQL Server 版本
|
||||||
├── oracle/
|
├── oracle/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
│ └── rxthinkcmf.thinkphp6.naivevue.sql # Oracle 版本
|
||||||
└── sqlite/
|
└── sqlite/
|
||||||
└── rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── rxthinkcmf.thinkphp6.naivevue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入使用,无需手动转换。
|
各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入使用,无需手动转换。
|
||||||
@@ -37,7 +37,7 @@ mysql -u root -p
|
|||||||
-- 创建项目数据库
|
-- 创建项目数据库
|
||||||
-- CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
|
-- CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
|
||||||
-- COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
|
-- COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
|
||||||
CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
EXIT;
|
EXIT;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ EXIT;
|
|||||||
# -u root -proot → 数据库用户名和密码
|
# -u root -proot → 数据库用户名和密码
|
||||||
# rxthinkcmf... → 目标数据库名
|
# rxthinkcmf... → 目标数据库名
|
||||||
# < file.sql → 将 SQL 文件内容作为 mysql 命令的输入
|
# < file.sql → 将 SQL 文件内容作为 mysql 命令的输入
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip 温馨提示
|
:::tip 温馨提示
|
||||||
@@ -63,7 +63,7 @@ mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thi
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql ; 数据库类型
|
TYPE = mysql ; 数据库类型
|
||||||
HOSTNAME = 127.0.0.1 ; 数据库地址
|
HOSTNAME = 127.0.0.1 ; 数据库地址
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue ; 数据库名
|
DATABASE = rxthinkcmf.thinkphp6.naivevue ; 数据库名
|
||||||
USERNAME = root ; 用户名
|
USERNAME = root ; 用户名
|
||||||
PASSWORD = root ; 密码
|
PASSWORD = root ; 密码
|
||||||
HOSTPORT = 3306 ; 端口
|
HOSTPORT = 3306 ; 端口
|
||||||
@@ -88,10 +88,10 @@ CREATE USER postgres WITH PASSWORD 'root';
|
|||||||
-- 创建项目数据库
|
-- 创建项目数据库
|
||||||
-- OWNER postgres → 指定数据库所有者
|
-- OWNER postgres → 指定数据库所有者
|
||||||
-- ENCODING 'UTF8' → 使用 UTF-8 编码
|
-- ENCODING 'UTF8' → 使用 UTF-8 编码
|
||||||
CREATE DATABASE "rxthinkcmf.thinkphp6.elevue" OWNER postgres ENCODING 'UTF8';
|
CREATE DATABASE "rxthinkcmf.thinkphp6.naivevue" OWNER postgres ENCODING 'UTF8';
|
||||||
|
|
||||||
-- 授予用户对该数据库的所有权限
|
-- 授予用户对该数据库的所有权限
|
||||||
GRANT ALL PRIVILEGES ON DATABASE "rxthinkcmf.thinkphp6.elevue" TO postgres;
|
GRANT ALL PRIVILEGES ON DATABASE "rxthinkcmf.thinkphp6.naivevue" TO postgres;
|
||||||
\q
|
\q
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ GRANT ALL PRIVILEGES ON DATABASE "rxthinkcmf.thinkphp6.elevue" TO postgres;
|
|||||||
# -U postgres → 连接用户
|
# -U postgres → 连接用户
|
||||||
# -d ... → 目标数据库
|
# -d ... → 目标数据库
|
||||||
# -f file.sql → 执行 SQL 文件
|
# -f file.sql → 执行 SQL 文件
|
||||||
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -f document/postgresql/rxthinkcmf.thinkphp6.elevue.sql
|
psql -U postgres -d rxthinkcmf.thinkphp6.naivevue -f document/postgresql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 配置 .env
|
### 3. 配置 .env
|
||||||
@@ -113,7 +113,7 @@ psql -U postgres -d rxthinkcmf.thinkphp6.elevue -f document/postgresql/rxthinkcm
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = pgsql ; 数据库类型改为 pgsql
|
TYPE = pgsql ; 数据库类型改为 pgsql
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = postgres ; PostgreSQL 默认用户
|
USERNAME = postgres ; PostgreSQL 默认用户
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 5432 ; PostgreSQL 默认端口
|
HOSTPORT = 5432 ; PostgreSQL 默认端口
|
||||||
@@ -146,7 +146,7 @@ touch /www/api/runtime/database.sqlite
|
|||||||
# sqlite3 → SQLite 命令行工具
|
# sqlite3 → SQLite 命令行工具
|
||||||
# 数据库文件路径 → SQLite 数据库存储在单个文件中
|
# 数据库文件路径 → SQLite 数据库存储在单个文件中
|
||||||
# < file.sql → 将 SQL 文件作为输入执行
|
# < file.sql → 将 SQL 文件作为输入执行
|
||||||
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.elevue.sql
|
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 配置 .env
|
### 3. 配置 .env
|
||||||
@@ -179,7 +179,7 @@ PREFIX = think_
|
|||||||
# -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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 导入 SQL 脚本
|
### 2. 导入 SQL 脚本
|
||||||
@@ -190,7 +190,7 @@ sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elev
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# -d rxthinkcmf... → 目标数据库
|
# -d rxthinkcmf... → 目标数据库
|
||||||
# -i file.sql → 执行 SQL 文件
|
# -i file.sql → 执行 SQL 文件
|
||||||
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.elevue -i document/sqlserver/rxthinkcmf.thinkphp6.elevue.sql
|
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.naivevue -i document/sqlserver/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 配置 .env
|
### 3. 配置 .env
|
||||||
@@ -199,7 +199,7 @@ sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.elevue -i document/sql
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = sqlsrv ; 数据库类型改为 sqlsrv
|
TYPE = sqlsrv ; 数据库类型改为 sqlsrv
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = sa ; SQL Server 默认管理员
|
USERNAME = sa ; SQL Server 默认管理员
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 1433 ; SQL Server 默认端口
|
HOSTPORT = 1433 ; SQL Server 默认端口
|
||||||
@@ -220,15 +220,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_thinkphp6_elevue"
|
CREATE TABLESPACE "rxthinkcmf_thinkphp6_naivevue"
|
||||||
DATAFILE '/u01/oradata/rxthinkcmf_thinkphp6_elevue.dbf'
|
DATAFILE '/u01/oradata/rxthinkcmf_thinkphp6_naivevue.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_thinkphp6_elevue";
|
DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_naivevue";
|
||||||
|
|
||||||
-- 授权
|
-- 授权
|
||||||
-- CONNECT → 连接权限
|
-- CONNECT → 连接权限
|
||||||
@@ -249,7 +249,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.thinkphp6.elevue.sql
|
sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 配置 .env
|
### 3. 配置 .env
|
||||||
@@ -258,7 +258,7 @@ sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.thinkphp6.elevue
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = oracle ; 数据库类型改为 oracle
|
TYPE = oracle ; 数据库类型改为 oracle
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
USERNAME = root
|
USERNAME = root
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 1521 ; Oracle 默认端口
|
HOSTPORT = 1521 ; Oracle 默认端口
|
||||||
@@ -288,13 +288,13 @@ curl http://localhost:8000/api/login -X POST \
|
|||||||
# MySQL:查询用户表记录数(验证表是否创建成功且有数据)
|
# MySQL:查询用户表记录数(验证表是否创建成功且有数据)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# -e → 执行 SQL 后退出
|
# -e → 执行 SQL 后退出
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue -e "SELECT COUNT(*) FROM think_user;"
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue -e "SELECT COUNT(*) FROM think_user;"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# PostgreSQL:查询用户表记录数
|
# PostgreSQL:查询用户表记录数
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# -c → 执行 SQL 命令
|
# -c → 执行 SQL 命令
|
||||||
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -c "SELECT COUNT(*) FROM think_user;"
|
psql -U postgres -d rxthinkcmf.thinkphp6.naivevue -c "SELECT COUNT(*) FROM think_user;"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# SQLite:查询用户表记录数
|
# SQLite:查询用户表记录数
|
||||||
@@ -305,7 +305,7 @@ sqlite3 /www/api/runtime/database.sqlite "SELECT COUNT(*) FROM think_user;"
|
|||||||
# SQL Server:查询用户表记录数
|
# SQL Server:查询用户表记录数
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# -Q → 执行查询后退出
|
# -Q → 执行查询后退出
|
||||||
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.elevue -Q "SELECT COUNT(*) FROM think_user;"
|
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.naivevue -Q "SELECT COUNT(*) FROM think_user;"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Oracle:查询用户表记录数(需先进入 SQL 命令行)
|
# Oracle:查询用户表记录数(需先进入 SQL 命令行)
|
||||||
@@ -338,7 +338,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.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot --default-character-set=utf8mb4 rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q: 表已存在错误
|
### Q: 表已存在错误
|
||||||
@@ -348,13 +348,13 @@ mysql -u root -proot --default-character-set=utf8mb4 rxthinkcmf.thinkphp6.elevue
|
|||||||
# 先删除旧数据库再重建(⚠️ 会清空所有数据)
|
# 先删除旧数据库再重建(⚠️ 会清空所有数据)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# DROP DATABASE IF EXISTS → 如果存在则删除
|
# DROP DATABASE IF EXISTS → 如果存在则删除
|
||||||
mysql -u root -proot -e "DROP DATABASE IF EXISTS \`rxthinkcmf.thinkphp6.elevue\`;"
|
mysql -u root -proot -e "DROP DATABASE IF EXISTS \`rxthinkcmf.thinkphp6.naivevue\`;"
|
||||||
|
|
||||||
# 创建数据库(指定字符集)
|
# 创建数据库(指定字符集)
|
||||||
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.thinkphp6.elevue\` DEFAULT CHARACTER SET utf8mb4;"
|
mysql -u root -proot -e "CREATE DATABASE \`rxthinkcmf.thinkphp6.naivevue\` DEFAULT CHARACTER SET utf8mb4;"
|
||||||
|
|
||||||
# 重新导入 SQL
|
# 重新导入 SQL
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q: PostgreSQL 导入报错
|
### Q: PostgreSQL 导入报错
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ Docker 方案适合快速体验项目功能,无需手动安装 PHP、MySQL 等
|
|||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
thinkphp6/
|
|
||||||
├── docker-compose.yml # 容器编排(项目根目录)
|
├── docker-compose.yml # 容器编排(项目根目录)
|
||||||
├── docker/
|
├── docker/
|
||||||
│ ├── Dockerfile # PHP-FPM 镜像定义
|
│ ├── Dockerfile # PHP-FPM 镜像定义
|
||||||
@@ -31,7 +30,7 @@ thinkphp6/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 从官方网站 (https://www.rxthink.cn) 下载授权源码包并解压
|
# 从官方网站 (https://www.rxthink.cn) 下载授权源码包并解压
|
||||||
cd thinkphp6
|
cd RXThinkCMF_ThinkPhp6_NaiveVue
|
||||||
|
|
||||||
# 配置应用环境变量
|
# 配置应用环境变量
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
@@ -127,12 +126,12 @@ services:
|
|||||||
- "${MYSQL_PORT:-3306}:3306" # 宿主机端口(默认 3306),可通过环境变量覆盖
|
- "${MYSQL_PORT:-3306}:3306" # 宿主机端口(默认 3306),可通过环境变量覆盖
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-Your_Strong_Password_2024} # root 密码
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-Your_Strong_Password_2024} # root 密码
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.thinkphp6.elevue} # 自动创建的数据库
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.thinkphp6.naivevue} # 自动创建的数据库
|
||||||
MYSQL_CHARSET: utf8mb4
|
MYSQL_CHARSET: utf8mb4
|
||||||
MYSQL_COLLATION: utf8mb4_general_ci
|
MYSQL_COLLATION: utf8mb4_general_ci
|
||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql # 数据持久化
|
- mysql_data:/var/lib/mysql # 数据持久化
|
||||||
- ./document/mysql/rxthinkcmf.thinkphp6.elevue.sql:/docker-entrypoint-initdb.d/init.sql:ro # 初始化 SQL
|
- ./document/mysql/rxthinkcmf.thinkphp6.naivevue.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
|
||||||
@@ -324,7 +323,7 @@ server {
|
|||||||
```ini
|
```ini
|
||||||
# Docker 环境变量(密码等敏感信息,切勿提交 Git)
|
# Docker 环境变量(密码等敏感信息,切勿提交 Git)
|
||||||
MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024
|
MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024
|
||||||
MYSQL_DATABASE=rxthinkcmf.thinkphp6.elevue
|
MYSQL_DATABASE=rxthinkcmf.thinkphp6.naivevue
|
||||||
REDIS_PASSWORD=Your_Redis_Password_2024
|
REDIS_PASSWORD=Your_Redis_Password_2024
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
```
|
```
|
||||||
@@ -409,10 +408,10 @@ docker-compose exec mysql bash
|
|||||||
# 数据库操作
|
# 数据库操作
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# 导入 SQL(手动方式)
|
# 导入 SQL(手动方式)
|
||||||
docker-compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
docker-compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
|
|
||||||
# 进入 MySQL 命令行
|
# 进入 MySQL 命令行
|
||||||
docker-compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue
|
docker-compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.naivevue
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# ThinkPHP 命令
|
# ThinkPHP 命令
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ SHOW_ERROR_MSG = true ; 显示错误详情(生产环境设
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql ; 数据库类型(mysql / pgsql / sqlsrv / oracle / sqlite)
|
TYPE = mysql ; 数据库类型(mysql / pgsql / sqlsrv / oracle / sqlite)
|
||||||
HOSTNAME = 127.0.0.1 ; 数据库服务器地址
|
HOSTNAME = 127.0.0.1 ; 数据库服务器地址
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue ; 数据库名
|
DATABASE = rxthinkcmf.thinkphp6.naivevue ; 数据库名
|
||||||
USERNAME = root ; 用户名
|
USERNAME = root ; 用户名
|
||||||
PASSWORD = root ; 密码
|
PASSWORD = root ; 密码
|
||||||
HOSTPORT = 3306 ; 端口
|
HOSTPORT = 3306 ; 端口
|
||||||
@@ -42,8 +42,8 @@ REFRESH_TTL = 604800 ; refresh_token 有效期(秒),默
|
|||||||
ISSUER = rxthinkcmf ; 签发者标识
|
ISSUER = rxthinkcmf ; 签发者标识
|
||||||
|
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf ; 上传根目录(绝对路径)
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads ; 上传根目录(绝对路径)
|
||||||
DOMAIN_URL = http://file.thinkphp6.elevue ; 文件访问域名
|
DOMAIN_URL = http://file.thinkphp6.naivevue ; 文件访问域名
|
||||||
IMAGE_EXT = gif,jpg,jpeg,png,bmp,webp,svg ; 允许的图片后缀
|
IMAGE_EXT = gif,jpg,jpeg,png,bmp,webp,svg ; 允许的图片后缀
|
||||||
FILE_EXT = gif,jpg,jpeg,png,bmp,webp,svg,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv,mp3,mp4,sql,js,css ; 允许的文件后缀
|
FILE_EXT = gif,jpg,jpeg,png,bmp,webp,svg,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv,mp3,mp4,sql,js,css ; 允许的文件后缀
|
||||||
MAX_SIZE = 10 ; 单文件最大大小(MB)
|
MAX_SIZE = 10 ; 单文件最大大小(MB)
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ MySQL 是项目默认推荐的关系型数据库,XAMPP 已自带,开箱即
|
|||||||
# 若前面有分号则去掉,保存后重启 XAMPP
|
# 若前面有分号则去掉,保存后重启 XAMPP
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
D:\xampp8.2\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
D:\xampp8.2\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -398,7 +398,7 @@ brew services start mysql
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -408,7 +408,7 @@ sudo systemctl enable mysql
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -418,7 +418,7 @@ sudo systemctl enable mysqld
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.naivevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -443,7 +443,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
psql -U postgres -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -454,8 +454,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.naivevue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -466,8 +466,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.naivevue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -479,8 +479,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp6.naivevue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.naivevue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -547,7 +547,7 @@ SQL Server 是微软商业数据库,XAMPP 已自带 pdo_sqlsrv 驱动,Window
|
|||||||
# extension=php_pdo_sqlsrv_82_ts_x64.dll
|
# extension=php_pdo_sqlsrv_82_ts_x64.dll
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -560,7 +560,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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -582,7 +582,7 @@ echo "extension=pdo_sqlsrv.so" | sudo tee /etc/php/8.2/mods-available/pdo_sqlsrv
|
|||||||
sudo phpenmod pdo_sqlsrv
|
sudo phpenmod pdo_sqlsrv
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -602,7 +602,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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.naivevue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -636,7 +636,7 @@ Oracle 是企业级商业数据库,需手动安装 Instant Client 和 pdo_oci
|
|||||||
# .env 配置
|
# .env 配置
|
||||||
# DATABASE.TYPE = oracle
|
# DATABASE.TYPE = oracle
|
||||||
# DATABASE.HOSTNAME = 127.0.0.1
|
# DATABASE.HOSTNAME = 127.0.0.1
|
||||||
# DATABASE.DATABASE = rxthinkcmf.thinkphp6.elevue
|
# DATABASE.DATABASE = rxthinkcmf.thinkphp6.naivevue
|
||||||
# DATABASE.USERNAME = root
|
# DATABASE.USERNAME = root
|
||||||
# DATABASE.PASSWORD = root
|
# DATABASE.PASSWORD = root
|
||||||
# DATABASE.HOSTPORT = 1521
|
# DATABASE.HOSTPORT = 1521
|
||||||
@@ -668,8 +668,8 @@ sudo phpenmod pdo_oci
|
|||||||
|
|
||||||
# 4. 创建表空间和用户
|
# 4. 创建表空间和用户
|
||||||
sqlplus system/root@localhost:1521/XE <<EOF
|
sqlplus system/root@localhost:1521/XE <<EOF
|
||||||
CREATE TABLESPACE "rxthinkcmf_thinkphp6_elevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_elevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
CREATE TABLESPACE "rxthinkcmf_thinkphp6_naivevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_naivevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
||||||
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_elevue";
|
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_naivevue";
|
||||||
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
||||||
GRANT UNLIMITED TABLESPACE TO "root";
|
GRANT UNLIMITED TABLESPACE TO "root";
|
||||||
EXIT;
|
EXIT;
|
||||||
@@ -692,8 +692,8 @@ sudo phpenmod pdo_oci
|
|||||||
|
|
||||||
# 4. 创建表空间和用户
|
# 4. 创建表空间和用户
|
||||||
sqlplus system/root@localhost:1521/XE <<EOF
|
sqlplus system/root@localhost:1521/XE <<EOF
|
||||||
CREATE TABLESPACE "rxthinkcmf_thinkphp6_elevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_elevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
CREATE TABLESPACE "rxthinkcmf_thinkphp6_naivevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_naivevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
||||||
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_elevue";
|
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_naivevue";
|
||||||
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
||||||
GRANT UNLIMITED TABLESPACE TO "root";
|
GRANT UNLIMITED TABLESPACE TO "root";
|
||||||
EXIT;
|
EXIT;
|
||||||
@@ -905,20 +905,23 @@ strict-ssl = false
|
|||||||
|
|
||||||
| 依赖 | 版本 | 说明 |
|
| 依赖 | 版本 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `vue` | ^3.4.27 | 核心框架 |
|
| `vue` | ^3.5.1 | 核心框架 |
|
||||||
| `element-plus` | ^2.7.4 | UI 组件库 |
|
| `naive-ui` | ^2.40.0 | UI 组件库 |
|
||||||
| `pinia` | ^2.1.7 | 状态管理 |
|
| `pinia` | ^2.2.2 | 状态管理 |
|
||||||
| `vue-router` | ^4.3.2 | 路由管理 |
|
| `vue-router` | ^4.4.3 | 路由管理 |
|
||||||
| `axios` | ^0.27.2 | HTTP 请求 |
|
| `axios` | ^1.7.7 | HTTP 请求 |
|
||||||
| `typescript` | ^4.9.5 | 类型系统 |
|
| `typescript` | ^4.9.5 | 类型系统 |
|
||||||
| `vite` | ^3.2.10 | 构建工具 |
|
| `vite` | ^5.4.3 | 构建工具 |
|
||||||
| `tinymce` | 5.10.3 | 富文本编辑器 |
|
| `tinymce` | 5.10.3 | 富文本编辑器 |
|
||||||
| `echarts` | ^5.5.0 | 图表库 |
|
| `@vueup/vue-quill` | 1.0.0-beta.8 | 富文本编辑器(Quill) |
|
||||||
| `tailwindcss` | ^3.4.3 | CSS 框架 |
|
| `echarts` | ^5.5.1 | 图表库 |
|
||||||
| `xlsx` | ^0.18.5 | Excel 处理 |
|
| `tailwindcss` | ^3.4.10 | CSS 框架 |
|
||||||
| `@element-plus/icons-vue` | ^2.3.1 | Element Plus 图标 |
|
| `less` | ^4.2.0 | CSS 预处理器 |
|
||||||
| `dayjs` | ^1.11.11 | 日期处理 |
|
| `xlsx` | ^0.17.5 | Excel 处理 |
|
||||||
| `lodash` | ^4.17.21 | 工具函数库 |
|
| `@vicons/antd` | ^0.10.0 | 图标(Ant Design 图标集) |
|
||||||
|
| `@vicons/ionicons5` | ^0.10.0 | 图标(Ionicons 图标集) |
|
||||||
|
| `date-fns` | ^2.30.0 | 日期处理 |
|
||||||
|
| `lodash-es` | ^4.17.21 | 工具函数库 |
|
||||||
|
|
||||||
## 开发工具推荐
|
## 开发工具推荐
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ php think run --port 8080
|
|||||||
A: 检查以下几点:
|
A: 检查以下几点:
|
||||||
1. MySQL 服务是否启动:`systemctl status mysql`
|
1. MySQL 服务是否启动:`systemctl status mysql`
|
||||||
2. `.env` 中的数据库配置是否正确
|
2. `.env` 中的数据库配置是否正确
|
||||||
3. 数据库用户是否有权限:`GRANT ALL ON \`rxthinkcmf.thinkphp6.elevue\`.* TO 'root'@'localhost'`
|
3. 数据库用户是否有权限:`GRANT ALL ON \`rxthinkcmf.thinkphp6.naivevue\`.* TO 'root'@'localhost'`
|
||||||
|
|
||||||
### Q: 登录返回 401
|
### Q: 登录返回 401
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ VITE_PORT = 8001
|
|||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
# 代理配置(JSON 数组格式:[["前缀","目标地址"]])
|
# 代理配置(JSON 数组格式:[["前缀","目标地址"]])
|
||||||
VITE_PROXY = [["/api","http://admin.thinkphp6.elevue/api"]]
|
VITE_PROXY = [["/api","http://admin.thinkphp6.naivevue/api"]]
|
||||||
|
|
||||||
# API 接口基础路径(留空,由代理处理)
|
# API 接口基础路径(留空,由代理处理)
|
||||||
VITE_GLOB_API_URL =
|
VITE_GLOB_API_URL =
|
||||||
@@ -50,7 +50,7 @@ VITE_GLOB_API_URL_PREFIX = /api
|
|||||||
VITE_GLOB_UPLOAD_URL = /api/upload
|
VITE_GLOB_UPLOAD_URL = /api/upload
|
||||||
|
|
||||||
# 图片访问地址
|
# 图片访问地址
|
||||||
VITE_GLOB_IMG_URL = http://file.thinkphp6.elevue
|
VITE_GLOB_IMG_URL = http://file.thinkphp6.naivevue
|
||||||
|
|
||||||
# 是否启用 Mock 数据(false = 使用真实后端 API)
|
# 是否启用 Mock 数据(false = 使用真实后端 API)
|
||||||
VITE_USE_MOCK = false
|
VITE_USE_MOCK = false
|
||||||
@@ -75,7 +75,7 @@ VITE_GLOB_API_URL_PREFIX = /api
|
|||||||
VITE_GLOB_UPLOAD_URL = /api/upload
|
VITE_GLOB_UPLOAD_URL = /api/upload
|
||||||
|
|
||||||
# 图片访问地址
|
# 图片访问地址
|
||||||
VITE_GLOB_IMG_URL = http://file.thinkphp6.elevue
|
VITE_GLOB_IMG_URL = http://file.thinkphp6.naivevue
|
||||||
|
|
||||||
# 是否启用 gzip 压缩
|
# 是否启用 gzip 压缩
|
||||||
VITE_BUILD_COMPRESS = gzip
|
VITE_BUILD_COMPRESS = gzip
|
||||||
@@ -117,7 +117,7 @@ server: {
|
|||||||
port: 8001, // 开发服务器端口(对应 VITE_PORT)
|
port: 8001, // 开发服务器端口(对应 VITE_PORT)
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://admin.thinkphp6.elevue/api', // 后端服务地址(对应 VITE_PROXY)
|
target: 'http://admin.thinkphp6.naivevue/api', // 后端服务地址(对应 VITE_PROXY)
|
||||||
changeOrigin: true, // 开启跨域(修改请求头的 Origin)
|
changeOrigin: true, // 开启跨域(修改请求头的 Origin)
|
||||||
rewrite: (path) => path, // 保持路径不变(不做路径替换)
|
rewrite: (path) => path, // 保持路径不变(不做路径替换)
|
||||||
},
|
},
|
||||||
@@ -135,7 +135,7 @@ server: {
|
|||||||
4. 登录成功后进入控制台页面
|
4. 登录成功后进入控制台页面
|
||||||
|
|
||||||
:::tip 代理原理
|
:::tip 代理原理
|
||||||
浏览器 → `http://localhost:8001/api/login` → Vite 代理 → `http://admin.thinkphp6.elevue/api/login` → 后端处理
|
浏览器 → `http://localhost:8001/api/login` → Vite 代理 → `http://admin.thinkphp6.naivevue/api/login` → 后端处理
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 构建生产版本
|
## 构建生产版本
|
||||||
@@ -187,5 +187,5 @@ server: {
|
|||||||
# -X POST → POST 方法
|
# -X POST → POST 方法
|
||||||
# -H → 设置 Content-Type 为 JSON
|
# -H → 设置 Content-Type 为 JSON
|
||||||
# -d → 发送 JSON 请求体
|
# -d → 发送 JSON 请求体
|
||||||
curl http://admin.thinkphp6.elevue/api/login -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}'
|
curl http://admin.thinkphp6.naivevue/api/login -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -136,21 +136,22 @@ class UserLogic extends BaseTenantLogic
|
|||||||
### 单条删除
|
### 单条删除
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<el-popconfirm title="确认删除该记录?" @confirm="handleDelete(row.id)">
|
<n-popconfirm @positive-click="handleDelete(row.id)">
|
||||||
<template #reference>
|
<template #trigger>
|
||||||
<el-button type="danger" size="small">删除</el-button>
|
<n-button type="error" size="small">删除</n-button>
|
||||||
</template>
|
</template>
|
||||||
</el-popconfirm>
|
确认删除该记录?
|
||||||
|
</n-popconfirm>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 批量删除
|
### 批量删除
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<el-button
|
<n-button
|
||||||
type="danger"
|
type="error"
|
||||||
:disabled="!selectedIds.length"
|
:disabled="!selectedIds.length"
|
||||||
@click="handleBatchDelete"
|
@click="handleBatchDelete"
|
||||||
>
|
>
|
||||||
批量删除 ({{ selectedIds.length }})
|
批量删除 ({{ selectedIds.length }})
|
||||||
</el-button>
|
</n-button>
|
||||||
```
|
```
|
||||||
@@ -18,23 +18,23 @@
|
|||||||
|
|
||||||
| 类别 | 技术选型 | 版本 | 说明 |
|
| 类别 | 技术选型 | 版本 | 说明 |
|
||||||
|------|----------|------|------|
|
|------|----------|------|------|
|
||||||
| 框架 | Vue | 3.4+ | 核心 UI 框架 |
|
| 框架 | Vue | 3.5+(实际 ^3.5.1) | 核心 UI 框架 |
|
||||||
| 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 |
|
| 语言 | TypeScript | 4.9+(实际 ^4.9.5) | 类型安全 |
|
||||||
| UI 库 | ElementPlus | 2.7+ | 组件库 |
|
| UI 库 | NaiveUI | 2.40+(实际 ^2.40.0) | 组件库 |
|
||||||
| 构建工具 | Vite | 3.2+(实际 ^3.2.10) | 开发服务器与构建 |
|
| 构建工具 | Vite | 5.4+(实际 ^5.4.3) | 开发服务器与构建 |
|
||||||
| 状态管理 | Pinia | 2.1+ | 全局状态管理 |
|
| 状态管理 | Pinia | 2.2+(实际 ^2.2.2) | 全局状态管理 |
|
||||||
| 路由 | Vue Router | 4.3+ | 路由管理 |
|
| 路由 | Vue Router | 4.4+(实际 ^4.4.3) | 路由管理 |
|
||||||
| HTTP | Axios | 0.27+(实际 ^0.27.2) | HTTP 请求(注:0.27.x 版本较老) |
|
| HTTP | Axios | 1.7+(实际 ^1.7.7) | HTTP 请求 |
|
||||||
| 富文本 | TinyMce | 5.10+ | 富文本编辑器 |
|
| 富文本 | TinyMce / Quill | 5.10+ | 富文本编辑器 |
|
||||||
| 图表 | ECharts | 5.5+ | 数据可视化 |
|
| 图表 | ECharts | 5.5+ | 数据可视化 |
|
||||||
| CSS | TailwindCSS | 3.4+ | 原子化 CSS |
|
| CSS | TailwindCSS + Less | 3.4+ | 原子化 CSS 与预处理 |
|
||||||
| 包管理 | pnpm | - | 高效包管理器 |
|
| 包管理 | pnpm | - | 高效包管理器 |
|
||||||
|
|
||||||
## 架构特点
|
## 架构特点
|
||||||
|
|
||||||
```text
|
```text
|
||||||
┌─────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────┐
|
||||||
│ 前端(Vue3 + ElementPlus) │
|
│ 前端(Vue3 + NaiveUI) │
|
||||||
│ 通过 API 调用后端服务,纯 JSON 交互 │
|
│ 通过 API 调用后端服务,纯 JSON 交互 │
|
||||||
└──────────────────────────┬──────────────────────────────┘
|
└──────────────────────────┬──────────────────────────────┘
|
||||||
│ HTTP (JSON)
|
│ HTTP (JSON)
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ php think db:migrate [选项]
|
|||||||
# --drop-target-first → 迁移前先删除目标库中的同名表(避免表已存在报错)
|
# --drop-target-first → 迁移前先删除目标库中的同名表(避免表已存在报错)
|
||||||
php think db:migrate \
|
php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=postgres --dst-password=123456 \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -125,9 +125,9 @@ php think db:migrate \
|
|||||||
# --dst-hostport=1433 → SQL Server 默认端口
|
# --dst-hostport=1433 → SQL Server 默认端口
|
||||||
php think db:migrate \
|
php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=sqlsrv --dst-host=127.0.0.1 --dst-hostport=1433 \
|
--dst-type=sqlsrv --dst-host=127.0.0.1 --dst-hostport=1433 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=sa --dst-password=123456 \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=sa --dst-password=123456 \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -141,8 +141,8 @@ php think db:migrate \
|
|||||||
# --dst-database → SQLite 文件路径(文件不存在时自动创建)
|
# --dst-database → SQLite 文件路径(文件不存在时自动创建)
|
||||||
php think db:migrate \
|
php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=sqlite --dst-database=runtime/rxthinkcmf.thinkphp6.elevue.db
|
--dst-type=sqlite --dst-database=runtime/rxthinkcmf.thinkphp6.naivevue.db
|
||||||
```
|
```
|
||||||
|
|
||||||
### MySQL → MySQL(同库复制)
|
### MySQL → MySQL(同库复制)
|
||||||
@@ -167,9 +167,9 @@ php think db:migrate \
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
php think db:migrate \
|
php think db:migrate \
|
||||||
--src-type=pgsql --src-host=127.0.0.1 --src-hostport=5432 \
|
--src-type=pgsql --src-host=127.0.0.1 --src-hostport=5432 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=postgres --src-password=123456 \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=postgres --src-password=123456 \
|
||||||
--dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
|
--dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=root --dst-password=root \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=root --dst-password=root \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -181,9 +181,9 @@ php think db:migrate \
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
php think db:migrate \
|
php think db:migrate \
|
||||||
--src-type=sqlsrv --src-host=127.0.0.1 --src-hostport=1433 \
|
--src-type=sqlsrv --src-host=127.0.0.1 --src-hostport=1433 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=sa --src-password=123456 \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=sa --src-password=123456 \
|
||||||
--dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
|
--dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=root --dst-password=root \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=root --dst-password=root \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -196,9 +196,9 @@ php think db:migrate \
|
|||||||
# --tables=think_user,think_role,think_menu → 只迁移这 3 张表
|
# --tables=think_user,think_role,think_menu → 只迁移这 3 张表
|
||||||
php think db:migrate \
|
php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=postgres --dst-password=123456 \
|
||||||
--tables=think_user,think_role,think_menu \
|
--tables=think_user,think_role,think_menu \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
```
|
```
|
||||||
@@ -210,17 +210,17 @@ php think db:migrate \
|
|||||||
```bash
|
```bash
|
||||||
$ php think db:migrate \
|
$ php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=postgres --dst-password=123456 \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
|
|
||||||
==============================================================
|
==============================================================
|
||||||
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: pgsql:rxthinkcmf.thinkphp6.elevue
|
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.naivevue → 目标: pgsql:rxthinkcmf.thinkphp6.naivevue
|
||||||
[迁移开始] 待迁移 25 张表
|
[迁移开始] 待迁移 25 张表
|
||||||
==============================================================
|
==============================================================
|
||||||
[·] [1/5] 建目标库...
|
[·] [1/5] 建目标库...
|
||||||
· 目标库 "rxthinkcmf.thinkphp6.elevue" 已就绪
|
· 目标库 "rxthinkcmf.thinkphp6.naivevue" 已就绪
|
||||||
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
||||||
[·] [2/5] 建表结构(25 张表)...
|
[·] [2/5] 建表结构(25 张表)...
|
||||||
[✓] [2/5] 建表结构 完成(耗时 440ms),25 张表,118 个索引,0 个外键
|
[✓] [2/5] 建表结构 完成(耗时 440ms),25 张表,118 个索引,0 个外键
|
||||||
@@ -266,17 +266,17 @@ $ php think db:migrate \
|
|||||||
```bash
|
```bash
|
||||||
$ php think db:migrate \
|
$ php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=sqlsrv --dst-host=127.0.0.1 --dst-hostport=1433 \
|
--dst-type=sqlsrv --dst-host=127.0.0.1 --dst-hostport=1433 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=sa --dst-password=123456 \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=sa --dst-password=123456 \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
|
|
||||||
==============================================================
|
==============================================================
|
||||||
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: sqlsrv:rxthinkcmf.thinkphp6.elevue
|
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.naivevue → 目标: sqlsrv:rxthinkcmf.thinkphp6.naivevue
|
||||||
[迁移开始] 待迁移 25 张表
|
[迁移开始] 待迁移 25 张表
|
||||||
==============================================================
|
==============================================================
|
||||||
[·] [1/5] 建目标库...
|
[·] [1/5] 建目标库...
|
||||||
· 目标库 "rxthinkcmf.thinkphp6.elevue" 已就绪
|
· 目标库 "rxthinkcmf.thinkphp6.naivevue" 已就绪
|
||||||
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
||||||
[·] [2/5] 建表结构(25 张表)...
|
[·] [2/5] 建表结构(25 张表)...
|
||||||
[✓] [2/5] 建表结构 完成(耗时 1.5s),25 张表,118 个索引,0 个外键
|
[✓] [2/5] 建表结构 完成(耗时 1.5s),25 张表,118 个索引,0 个外键
|
||||||
@@ -298,13 +298,13 @@ $ php think db:migrate \
|
|||||||
```bash
|
```bash
|
||||||
$ php think db:migrate \
|
$ php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=oracle --dst-host=127.0.0.1 --dst-hostport=1521 \
|
--dst-type=oracle --dst-host=127.0.0.1 --dst-hostport=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.thinkphp6.elevue → 目标: oracle:rxthinkcmf.thinkphp6.elevue
|
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.naivevue → 目标: oracle:rxthinkcmf.thinkphp6.naivevue
|
||||||
[迁移开始] 待迁移 25 张表
|
[迁移开始] 待迁移 25 张表
|
||||||
==============================================================
|
==============================================================
|
||||||
[·] [1/5] 建目标库...
|
[·] [1/5] 建目标库...
|
||||||
@@ -347,15 +347,15 @@ $ php think db:migrate \
|
|||||||
```bash
|
```bash
|
||||||
$ php think db:migrate \
|
$ php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=sqlite --dst-database=runtime/rxthinkcmf.thinkphp6.elevue.db
|
--dst-type=sqlite --dst-database=runtime/rxthinkcmf.thinkphp6.naivevue.db
|
||||||
|
|
||||||
==============================================================
|
==============================================================
|
||||||
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: sqlite:runtime/rxthinkcmf.thinkphp6.elevue.db
|
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.naivevue → 目标: sqlite:runtime/rxthinkcmf.thinkphp6.naivevue.db
|
||||||
[迁移开始] 待迁移 25 张表
|
[迁移开始] 待迁移 25 张表
|
||||||
==============================================================
|
==============================================================
|
||||||
[·] [1/5] 建目标库...
|
[·] [1/5] 建目标库...
|
||||||
· 目标库 "runtime/rxthinkcmf.thinkphp6.elevue.db" 已就绪
|
· 目标库 "runtime/rxthinkcmf.thinkphp6.naivevue.db" 已就绪
|
||||||
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
||||||
[·] [2/5] 建表结构(25 张表)...
|
[·] [2/5] 建表结构(25 张表)...
|
||||||
[✓] [2/5] 建表结构 完成(耗时 1s),25 张表,118 个索引,0 个外键
|
[✓] [2/5] 建表结构 完成(耗时 1s),25 张表,118 个索引,0 个外键
|
||||||
@@ -378,18 +378,18 @@ $ php think db:migrate \
|
|||||||
```bash
|
```bash
|
||||||
$ php think db:migrate \
|
$ php think db:migrate \
|
||||||
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
--src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
|
||||||
--src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
|
--src-database=rxthinkcmf.thinkphp6.naivevue --src-username=root --src-password=root \
|
||||||
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
--dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
|
||||||
--dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
|
--dst-database=rxthinkcmf.thinkphp6.naivevue --dst-username=postgres --dst-password=123456 \
|
||||||
--tables=think_user,think_role,think_menu \
|
--tables=think_user,think_role,think_menu \
|
||||||
--drop-target-first
|
--drop-target-first
|
||||||
|
|
||||||
==============================================================
|
==============================================================
|
||||||
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: pgsql:rxthinkcmf.thinkphp6.elevue
|
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.naivevue → 目标: pgsql:rxthinkcmf.thinkphp6.naivevue
|
||||||
[迁移开始] 待迁移 3 张表
|
[迁移开始] 待迁移 3 张表
|
||||||
==============================================================
|
==============================================================
|
||||||
[·] [1/5] 建目标库...
|
[·] [1/5] 建目标库...
|
||||||
· 目标库 "rxthinkcmf.thinkphp6.elevue" 已就绪
|
· 目标库 "rxthinkcmf.thinkphp6.naivevue" 已就绪
|
||||||
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
[✓] [1/5] 建目标库 完成(耗时 0ms)
|
||||||
[·] [2/5] 建表结构(3 张表)...
|
[·] [2/5] 建表结构(3 张表)...
|
||||||
· 已删除目标表: think_user
|
· 已删除目标表: think_user
|
||||||
@@ -789,7 +789,7 @@ D:\xampp8.2\php\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.thinkphp6.elevue`)各驱动均已适配
|
8. 含点号的数据库名(如 `rxthinkcmf.thinkphp6.naivevue`)各驱动均已适配
|
||||||
9. 索引、外键和注释在建表阶段同步迁移,使用 `--drop-target-first` 重建时会一并重建
|
9. 索引、外键和注释在建表阶段同步迁移,使用 `--drop-target-first` 重建时会一并重建
|
||||||
10. 外键约束在所有表创建后统一添加,避免引用顺序问题
|
10. 外键约束在所有表创建后统一添加,避免引用顺序问题
|
||||||
11. 命令退出码:成功返回 0,失败或行数校验未通过返回 1(可用于 CI/CD 判断)
|
11. 命令退出码:成功返回 0,失败或行数校验未通过返回 1(可用于 CI/CD 判断)
|
||||||
@@ -850,7 +850,7 @@ A: Oracle 没有原生自增语法。迁移工具会自动为自增列创建序
|
|||||||
|
|
||||||
### Q: 含点号的数据库名支持吗?
|
### Q: 含点号的数据库名支持吗?
|
||||||
|
|
||||||
A: 支持。如 `rxthinkcmf.thinkphp6.elevue` 这类含点号的库名,各驱动均已适配。
|
A: 支持。如 `rxthinkcmf.thinkphp6.naivevue` 这类含点号的库名,各驱动均已适配。
|
||||||
|
|
||||||
### Q: SQLite 作为目标有什么限制?
|
### Q: SQLite 作为目标有什么限制?
|
||||||
|
|
||||||
|
|||||||
@@ -378,28 +378,37 @@ public function export(array $params = []): string
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-upload
|
<n-upload
|
||||||
:action="'/api/example/import'"
|
:action="'/api/example/import'"
|
||||||
:headers="{ Authorization: `Bearer ${token}` }"
|
:headers="uploadHeaders"
|
||||||
:on-success="handleImportSuccess"
|
@change="handleImportChange"
|
||||||
:on-error="handleImportError"
|
|
||||||
accept=".xls,.xlsx"
|
accept=".xls,.xlsx"
|
||||||
>
|
>
|
||||||
<el-button type="primary">导入</el-button>
|
<n-button type="primary">导入</n-button>
|
||||||
</el-upload>
|
</n-upload>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const handleImportSuccess = (response) => {
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
const uploadHeaders = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const handleImportChange = (data) => {
|
||||||
|
if (data.file.status !== 'finished') return;
|
||||||
|
const response = JSON.parse(data.file.event.target.response);
|
||||||
if (response.code === 0) {
|
if (response.code === 0) {
|
||||||
const { count, errors } = response.data;
|
const { count, errors } = response.data;
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
ElMessage.success(`成功导入 ${count} 条数据`);
|
message.success(`成功导入 ${count} 条数据`);
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning(`成功 ${count} 条,失败 ${errors.length} 条`);
|
message.warning(`成功 ${count} 条,失败 ${errors.length} 条`);
|
||||||
// 可以展示错误详情
|
// 可以展示错误详情
|
||||||
}
|
}
|
||||||
refreshTable(); // 刷新列表
|
refreshTable(); // 刷新列表
|
||||||
|
} else {
|
||||||
|
message.error(response.msg || '导入失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ return [
|
|||||||
```ini
|
```ini
|
||||||
; .env
|
; .env
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads
|
||||||
```
|
```
|
||||||
|
|
||||||
**要求:**
|
**要求:**
|
||||||
@@ -47,10 +47,10 @@ UPLOAD_DIR = D:/uploads/rxthinkcmf
|
|||||||
|
|
||||||
| 环境 | 路径示例 |
|
| 环境 | 路径示例 |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| Windows 开发 | `D:/uploads/rxthinkcmf` |
|
| Windows 开发 | `D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads` |
|
||||||
| Linux 开发 | `/home/www/uploads/rxthinkcmf` |
|
| Linux 开发 | `/home/www/uploads` |
|
||||||
| Linux 生产 | `/data/uploads/rxthinkcmf` |
|
| Linux 生产 | `/opt/data/uploads` |
|
||||||
| Docker | `/uploads/rxthinkcmf`(挂载卷) |
|
| Docker | `/data/apps/uploads`(挂载卷) |
|
||||||
|
|
||||||
### domain_url — 文件访问域名
|
### domain_url — 文件访问域名
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ DOMAIN_URL = https://cdn.example.com
|
|||||||
| 环境 | 域名示例 |
|
| 环境 | 域名示例 |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| 本地开发 | (留空,使用相对路径) |
|
| 本地开发 | (留空,使用相对路径) |
|
||||||
| 测试环境 | `https://test-cdn.example.com` |
|
| 测试环境 | `http://file.thinkphp6.naivevue` |
|
||||||
| 生产环境 | `https://cdn.example.com` |
|
| 生产环境 | `https://cdn.example.com` |
|
||||||
| 对象存储 | `https://bucket-name.oss-cn-hangzhou.aliyuncs.com` |
|
| 对象存储 | `https://bucket-name.oss-cn-hangzhou.aliyuncs.com` |
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ FILE_EXT = gif,jpg,jpeg,png,bmp,webp,svg,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,
|
|||||||
|
|
||||||
```ini
|
```ini
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp6_NaiveVue\uploads
|
||||||
DOMAIN_URL =
|
DOMAIN_URL =
|
||||||
MAX_SIZE = 10
|
MAX_SIZE = 10
|
||||||
```
|
```
|
||||||
@@ -133,8 +133,8 @@ MAX_SIZE = 10
|
|||||||
|
|
||||||
```ini
|
```ini
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = /home/www/uploads/rxthinkcmf
|
UPLOAD_DIR = /home/www/uploads
|
||||||
DOMAIN_URL = https://test-cdn.example.com
|
DOMAIN_URL = http://file.thinkphp6.naivevue
|
||||||
MAX_SIZE = 10
|
MAX_SIZE = 10
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ MAX_SIZE = 10
|
|||||||
|
|
||||||
```ini
|
```ini
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = /data/uploads/rxthinkcmf
|
UPLOAD_DIR = /opt/data/uploads
|
||||||
DOMAIN_URL = https://cdn.example.com
|
DOMAIN_URL = https://cdn.example.com
|
||||||
MAX_SIZE = 10
|
MAX_SIZE = 10
|
||||||
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp
|
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp
|
||||||
@@ -154,7 +154,7 @@ FILE_EXT = jpg,jpeg,png,gif,bmp,webp,doc,docx,xls,xlsx,pdf,zip,rar
|
|||||||
```ini
|
```ini
|
||||||
; 如果使用阿里云 OSS 等对象存储,domain_url 设为 OSS 域名
|
; 如果使用阿里云 OSS 等对象存储,domain_url 设为 OSS 域名
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = /data/uploads/rxthinkcmf
|
UPLOAD_DIR = /opt/data/uploads
|
||||||
DOMAIN_URL = https://your-bucket.oss-cn-hangzhou.aliyuncs.com
|
DOMAIN_URL = https://your-bucket.oss-cn-hangzhou.aliyuncs.com
|
||||||
MAX_SIZE = 50
|
MAX_SIZE = 50
|
||||||
```
|
```
|
||||||
@@ -235,12 +235,12 @@ class ArticleLogic extends BaseTenantLogic
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
配置:
|
配置:
|
||||||
upload_dir = /data/uploads/rxthinkcmf
|
upload_dir = /opt/data/uploads
|
||||||
fileSaveDir = article
|
fileSaveDir = article
|
||||||
|
|
||||||
结果:
|
结果:
|
||||||
临时文件: /data/uploads/rxthinkcmf/temp/20260920/abc.jpg
|
临时文件: /opt/data/uploads/temp/20260920/abc.jpg
|
||||||
正式文件: /data/uploads/rxthinkcmf/article/20260920/abc.jpg
|
正式文件: /opt/data/uploads/article/20260920/abc.jpg
|
||||||
数据库存: /uploads/article/20260920/abc.jpg
|
数据库存: /uploads/article/20260920/abc.jpg
|
||||||
前端显示: https://cdn.example.com/uploads/article/20260920/abc.jpg
|
前端显示: https://cdn.example.com/uploads/article/20260920/abc.jpg
|
||||||
```
|
```
|
||||||
@@ -252,7 +252,7 @@ class ArticleLogic extends BaseTenantLogic
|
|||||||
```nginx
|
```nginx
|
||||||
# 配置 Nginx 直接处理文件请求,不经过 PHP
|
# 配置 Nginx 直接处理文件请求,不经过 PHP
|
||||||
location /uploads/ {
|
location /uploads/ {
|
||||||
alias /data/uploads/rxthinkcmf/;
|
alias /opt/data/uploads/;
|
||||||
expires 30d;
|
expires 30d;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
@@ -285,8 +285,8 @@ location ~ /\. {
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux
|
# Linux
|
||||||
chmod -R 755 /data/uploads/rxthinkcmf
|
chmod -R 755 /opt/data/uploads
|
||||||
chown -R www:www /data/uploads/rxthinkcmf
|
chown -R www:www /opt/data/uploads
|
||||||
```
|
```
|
||||||
|
|
||||||
### 问题 2:上传后图片不显示
|
### 问题 2:上传后图片不显示
|
||||||
@@ -319,7 +319,7 @@ max_execution_time = 300
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 定时任务:每天凌晨清理 1 天前的临时文件
|
# 定时任务:每天凌晨清理 1 天前的临时文件
|
||||||
find /data/uploads/rxthinkcmf/temp/ -type f -mtime +1 -delete
|
find /opt/data/uploads/temp/ -type f -mtime +1 -delete
|
||||||
```
|
```
|
||||||
|
|
||||||
## 安全建议
|
## 安全建议
|
||||||
|
|||||||
@@ -363,35 +363,46 @@ file: <二进制图片>
|
|||||||
|
|
||||||
## 前端集成
|
## 前端集成
|
||||||
|
|
||||||
### Element Plus 上传组件
|
### NaiveUI 上传组件
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-upload
|
<n-upload
|
||||||
:action="'/api/upload/uploadFile'"
|
:action="'/api/upload/uploadFile'"
|
||||||
:headers="{ Authorization: `Bearer ${token}` }"
|
:headers="uploadHeaders"
|
||||||
:on-success="handleUploadSuccess"
|
@before-upload="beforeUpload"
|
||||||
:before-upload="beforeUpload"
|
@change="handleUploadChange"
|
||||||
>
|
>
|
||||||
<el-button type="primary">上传文件</el-button>
|
<n-button type="primary">上传文件</n-button>
|
||||||
</el-upload>
|
</n-upload>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const token = localStorage.getItem('access_token');
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
const beforeUpload = (file) => {
|
const message = useMessage();
|
||||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
const token = localStorage.getItem('access_token');
|
||||||
|
const uploadHeaders = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const beforeUpload = ({ file }) => {
|
||||||
|
const isLt10M = file.file.size / 1024 / 1024 < 10;
|
||||||
if (!isLt10M) {
|
if (!isLt10M) {
|
||||||
ElMessage.error('文件大小不能超过 10MB');
|
message.error('文件大小不能超过 10MB');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return isLt10M;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadSuccess = (response) => {
|
const handleUploadChange = (data) => {
|
||||||
// response.data.fileUrl 是完整 URL
|
if (data.file.status === 'finished') {
|
||||||
// 提交表单时使用此 URL
|
const res = JSON.parse(data.file.event.target.response);
|
||||||
form.value.avatar = response.data.fileUrl;
|
if (res.code === 0) {
|
||||||
|
// res.data.fileUrl 是完整 URL,提交表单时使用此 URL
|
||||||
|
form.value.avatar = res.data.fileUrl;
|
||||||
|
} else {
|
||||||
|
message.error(res.msg || '上传失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -131,13 +131,13 @@ class ExampleController extends BaseController
|
|||||||
|
|
||||||
这些模块不仅可以直接使用,也是学习本框架开发模式的最佳参考。
|
这些模块不仅可以直接使用,也是学习本框架开发模式的最佳参考。
|
||||||
|
|
||||||
## Vue3 + ElementPlus 前端
|
## Vue3 + NaiveUI 前端
|
||||||
|
|
||||||
前端采用主流技术栈,开发体验优秀:
|
前端采用主流技术栈,开发体验优秀:
|
||||||
|
|
||||||
- **Vue3 Composition API**:更灵活的组件逻辑组织
|
- **Vue3 Composition API**:更灵活的组件逻辑组织
|
||||||
- **TypeScript**:类型安全,减少运行时错误
|
- **TypeScript**:类型安全,减少运行时错误
|
||||||
- **ElementPlus**:成熟的企业级 UI 组件库
|
- **NaiveUI**:成熟的企业级 UI 组件库
|
||||||
- **Pinia**:轻量级状态管理
|
- **Pinia**:轻量级状态管理
|
||||||
- **Vite**:极速的开发服务器和构建工具
|
- **Vite**:极速的开发服务器和构建工具
|
||||||
- **动态路由**:菜单和路由由后端驱动,前端无需硬编码
|
- **动态路由**:菜单和路由由后端驱动,前端无需硬编码
|
||||||
@@ -157,7 +157,7 @@ pnpm build # 生产环境构建
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 从官方网站下载源码包并解压
|
# 1. 从官方网站下载源码包并解压
|
||||||
cd thinkphp6
|
cd RXThinkCMF_ThinkPhp6_NaiveVue
|
||||||
|
|
||||||
# 2. 安装依赖
|
# 2. 安装依赖
|
||||||
composer install
|
composer install
|
||||||
@@ -165,7 +165,7 @@ composer install
|
|||||||
# 3. 配置 .env(数据库、JWT 密钥)
|
# 3. 配置 .env(数据库、JWT 密钥)
|
||||||
|
|
||||||
# 4. 导入数据库
|
# 4. 导入数据库
|
||||||
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
|
mysql -u root -proot rxthinkcmf.thinkphp6.naivevue < document/mysql/rxthinkcmf.thinkphp6.naivevue.sql
|
||||||
|
|
||||||
# 5. 启动后端
|
# 5. 启动后端
|
||||||
php think run
|
php think run
|
||||||
@@ -206,7 +206,7 @@ TYPE = sqlite ; SQLite
|
|||||||
|
|
||||||
- ThinkPHP 版本持续升级至 6.1.x
|
- ThinkPHP 版本持续升级至 6.1.x
|
||||||
- PHP 版本要求 8.2+
|
- PHP 版本要求 8.2+
|
||||||
- 前端从 Vue 2 升级到 Vue3 + ElementPlus
|
- 前端从 Vue 2 升级到 Vue3 + NaiveUI
|
||||||
- 代码生成器功能不断增强
|
- 代码生成器功能不断增强
|
||||||
- 安全机制持续完善
|
- 安全机制持续完善
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
//
|
//
|
||||||
// themeConfig: {
|
// themeConfig: {
|
||||||
// editLink: {
|
// editLink: {
|
||||||
// pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
// pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue",
|
||||||
// text: "Suggest changes to this page",
|
// text: "Suggest changes to this page",
|
||||||
// },
|
// },
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const sharedConfig = defineConfig({
|
|||||||
socialLinks: [
|
socialLinks: [
|
||||||
{
|
{
|
||||||
icon: "github",
|
icon: "github",
|
||||||
link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ export const sharedConfig = defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue",
|
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_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 ThinkPHP6+ElementPlus 开发文档";
|
export const META_TITLE = "RXThinkCMF ThinkPHP8+AntDesign 开发文档";
|
||||||
export const META_DESCRIPTION =
|
export const META_DESCRIPTION =
|
||||||
"RXThinkCMF 企业级前后端分离后台管理系统 - ThinkPHP6.1 + Vue3 + ElementPlus";
|
"RXThinkCMF 企业级前后端分离后台管理系统 - ThinkPHP8 + Vue3 + AntDesign";
|
||||||
|
|
||||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||||
description: META_DESCRIPTION,
|
description: META_DESCRIPTION,
|
||||||
@@ -13,11 +13,24 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
["meta", { property: "twitter:url", content: META_URL }],
|
["meta", { property: "twitter:url", content: META_URL }],
|
||||||
["meta", { property: "twitter:title", content: META_TITLE }],
|
["meta", { property: "twitter:title", content: META_TITLE }],
|
||||||
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
["meta", { property: "twitter:description", content: META_DESCRIPTION }],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{},
|
||||||
|
`
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?8d61d55ff98ff25b565a21dbdcd55b9b";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
editLink: {
|
editLink: {
|
||||||
pattern: "https://gitee.com/rxthink/thinkphp6",
|
pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue",
|
||||||
text: "对本页提出修改建议",
|
text: "对本页提出修改建议",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -147,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_ThinkPhp6_EleVue" },
|
{ text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue" },
|
||||||
{ text: "ThinkPHP 官网", link: "https://www.thinkphp.cn/" },
|
{ text: "ThinkPHP 官网", link: "https://www.thinkphp.cn/" },
|
||||||
{ text: "Vue3 文档", link: "https://cn.vuejs.org/" },
|
{ text: "Vue3 文档", link: "https://cn.vuejs.org/" },
|
||||||
{ text: "ElementPlus", link: "https://element-plus.org/zh-CN/" },
|
{ text: "Ant Design Vue", link: "https://www.antdv.com/" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -489,7 +502,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
|||||||
{ text: "K. 贡献指南", link: "/zh/other/contributing.html" },
|
{ text: "K. 贡献指南", link: "/zh/other/contributing.html" },
|
||||||
{ text: "L. 免责声明", link: "/zh/other/declare.html" },
|
{ text: "L. 免责声明", link: "/zh/other/declare.html" },
|
||||||
{ text: "M. 广告赞助", link: "/zh/other/sponsor.html" },
|
{ text: "M. 广告赞助", link: "/zh/other/sponsor.html" },
|
||||||
{ text: "N. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue" },
|
{ text: "N. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp8_AntdVue" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 852 KiB After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 51 KiB |
@@ -128,14 +128,16 @@ export function getDictItemList(code: string) {
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
<a-select v-model:value="formData.status" placeholder="请选择状态" allow-clear>
|
||||||
<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"
|
||||||
/>
|
:label="item.name"
|
||||||
</el-select>
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Axios 响应拦截器
|
// Axios 响应拦截器
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
axios.interceptors.response.use(response => {
|
axios.interceptors.response.use(response => {
|
||||||
const { code, msg, data } = response.data;
|
const { code, msg, data } = response.data;
|
||||||
|
|
||||||
@@ -67,12 +69,12 @@ axios.interceptors.response.use(response => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (code === 403) {
|
if (code === 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));
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -253,6 +253,7 @@ $this->fail('缺少id参数', 1);
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/utils/http/axios/index.ts
|
// src/utils/http/axios/index.ts
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
response => {
|
response => {
|
||||||
@@ -272,16 +273,16 @@ axios.interceptors.response.use(
|
|||||||
|
|
||||||
// 权限不足
|
// 权限不足
|
||||||
if (code === 403) {
|
if (code === 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);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -305,9 +306,9 @@ const handleImport = async (file) => {
|
|||||||
const res = await importUsers(file);
|
const res = await importUsers(file);
|
||||||
// res = { count: 8, errors: [{ row: 3, reason: "..." }] }
|
// res = { count: 8, errors: [{ row: 3, 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} 条`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ Authorization: Bearer <access_token>
|
|||||||
路由守卫支持通过 URL 参数传递 Token,适用于 SSO 单点登录场景:
|
路由守卫支持通过 URL 参数传递 Token,适用于 SSO 单点登录场景:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
http://localhost:8001/?token=<access_token>
|
http://localhost:8002/?token=<access_token>
|
||||||
```
|
```
|
||||||
|
|
||||||
**前端处理逻辑:**
|
**前端处理逻辑:**
|
||||||
|
|||||||
@@ -95,51 +95,56 @@ gif, jpg, jpeg, png, bmp, webp, svg
|
|||||||
|
|
||||||
## 前端使用
|
## 前端使用
|
||||||
|
|
||||||
### Element Plus Upload 组件
|
### Ant Design 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"
|
||||||
|
@change="handleUploadSuccess"
|
||||||
>
|
>
|
||||||
<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'"
|
||||||
:headers="{ Authorization: `Bearer ${token}` }"
|
:headers="{ Authorization: `Bearer ${token}` }"
|
||||||
:on-success="handleImageSuccess"
|
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
|
@change="handleImageSuccess"
|
||||||
>
|
>
|
||||||
<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');
|
||||||
|
|
||||||
|
// 返回 false 阻止上传
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
return isLt10M;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadSuccess = (response) => {
|
// onChange 回调中从 file.response 取上传结果
|
||||||
if (response.code === 0) {
|
const handleUploadSuccess = ({ file }) => {
|
||||||
form.value.file = response.data.fileUrl;
|
if (file.response?.code === 0) {
|
||||||
|
form.value.file = file.response.data.fileUrl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageSuccess = (response) => {
|
const handleImageSuccess = ({ file }) => {
|
||||||
if (response.code === 0) {
|
if (file.response?.code === 0) {
|
||||||
form.value.avatar = response.data.fileUrl;
|
form.value.avatar = file.response.data.fileUrl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -189,24 +194,26 @@ 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="handleAvatarSuccess"
|
||||||
>
|
>
|
||||||
<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 handleAvatarSuccess = ({ file }) => {
|
||||||
form.value.avatar = response.data.fileUrl;
|
if (file.response?.code === 0) {
|
||||||
|
form.value.avatar = file.response.data.fileUrl;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
@@ -234,7 +241,7 @@ BaseLogic::add()
|
|||||||
```ini
|
```ini
|
||||||
; .env
|
; .env
|
||||||
[FILE]
|
[FILE]
|
||||||
UPLOAD_DIR = D:/uploads/rxthinkcmf ; 上传根目录
|
UPLOAD_DIR = D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp8_AntdVue\uploads ; 上传根目录
|
||||||
DOMAIN_URL = https://cdn.example.com ; 文件访问域名
|
DOMAIN_URL = https://cdn.example.com ; 文件访问域名
|
||||||
MAX_SIZE = 10 ; 单文件最大 10MB
|
MAX_SIZE = 10 ; 单文件最大 10MB
|
||||||
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp,svg
|
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp,svg
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
| Region | `components/Region/` | 行政区划 | `value`、`level` |
|
| Region | `components/Region/` | 行政区划 | `value`、`level` |
|
||||||
| Cropper | `components/Cropper/` | 头像裁剪 | `image`、`aspectRatio` |
|
| Cropper | `components/Cropper/` | 头像裁剪 | `image`、`aspectRatio` |
|
||||||
| NumberInput | `components/numberInput/` | 数字输入 | `value`、`min`、`max` |
|
| NumberInput | `components/numberInput/` | 数字输入 | `value`、`min`、`max` |
|
||||||
| PriceInput | `components/priceInput/` | 价格输入 | `value`、`precision` |
|
|
||||||
|
|
||||||
## Excel
|
## Excel
|
||||||
|
|
||||||
@@ -41,7 +40,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 组件 |
|
||||||
|
|
||||||
@@ -51,20 +49,34 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicTable @register="registerTable" />
|
<BasicTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="loadDataTable"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
ref="tableRef"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicTable, useTable } from '@/components/Table';
|
import { ref } from 'vue';
|
||||||
|
import { getExamplePage } from '@/api/tool/example';
|
||||||
|
|
||||||
const [registerTable] = useTable({
|
const tableRef = ref();
|
||||||
api: getExamplePage,
|
const columns = [
|
||||||
columns: [
|
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||||
{ title: 'ID', field: 'id', width: 80 },
|
{ title: '名称', dataIndex: 'name' },
|
||||||
{ title: '名称', field: 'name' },
|
{ title: '状态', dataIndex: 'statusText', width: 100 },
|
||||||
{ title: '状态', field: 'statusText', width: 100 },
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 180 },
|
||||||
],
|
];
|
||||||
});
|
|
||||||
|
const loadDataTable = async (params) => await getExamplePage(params);
|
||||||
|
const handleEdit = (record) => {};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,8 +92,13 @@ import { BasicForm, useForm } from '@/components/Form';
|
|||||||
|
|
||||||
const [registerForm] = useForm({
|
const [registerForm] = useForm({
|
||||||
schemas: [
|
schemas: [
|
||||||
{ field: 'name', label: '名称', component: 'Input', required: true },
|
{
|
||||||
{ field: 'status', label: '状态', component: 'Select', componentProps: { options: [...] } },
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
component: 'Input',
|
||||||
|
rules: [{ required: true, message: '请输入名称', trigger: ['blur'] }],
|
||||||
|
},
|
||||||
|
{ name: 'status', label: '状态', component: 'Select', componentProps: { options: [...] } },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -91,15 +108,19 @@ const [registerForm] = useForm({
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<BasicModal @register="registerModal" @ok="handleSubmit" />
|
<basicModal @register="registerModal" @ok="handleSubmit">
|
||||||
|
<template #default>
|
||||||
|
<!-- 表单内容 -->
|
||||||
|
</template>
|
||||||
|
</basicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BasicModal, useModal } from '@/components/Modal';
|
import { basicModal, useModal } from '@/components/Modal';
|
||||||
|
|
||||||
const [registerModal, { openModal, closeModal }] = useModal();
|
const [registerModal, { openModal, closeModal }] = useModal();
|
||||||
|
|
||||||
const handleAdd = () => openModal(true, { isUpdate: false });
|
const handleAdd = () => openModal();
|
||||||
const handleSubmit = async () => { /* ... */ closeModal(); };
|
const handleSubmit = async () => { /* ... */ closeModal(); };
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本
|
├── mysql/rxthinkcmf.thinkphp8.antdvue.sql # MySQL 版本
|
||||||
├── postgresql/rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
├── postgresql/rxthinkcmf.thinkphp8.antdvue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
├── sqlserver/rxthinkcmf.thinkphp8.antdvue.sql # SQL Server 版本
|
||||||
├── oracle/rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
├── oracle/rxthinkcmf.thinkphp8.antdvue.sql # Oracle 版本
|
||||||
└── sqlite/rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── sqlite/rxthinkcmf.thinkphp8.antdvue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。
|
各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。
|
||||||
|
|||||||
@@ -28,9 +28,9 @@
|
|||||||
| TypeScript | - | JavaScript 超集,类型安全 |
|
| TypeScript | - | JavaScript 超集,类型安全 |
|
||||||
| Vite | - | 前端构建工具 |
|
| Vite | - | 前端构建工具 |
|
||||||
| Pinia | - | Vue 3 官方状态管理库 |
|
| Pinia | - | Vue 3 官方状态管理库 |
|
||||||
| ElementPlus | - | Vue 3 UI 组件库 |
|
| AntDesign Vue | - | Vue 3 UI 组件库 |
|
||||||
| Axios | - | HTTP 请求库 |
|
| Axios | - | HTTP 请求库 |
|
||||||
| TailwindCSS | - | 原子化 CSS 框架 |
|
| WindiCSS | - | 原子化 CSS 框架(兼容 Tailwind 语法) |
|
||||||
| TinyMce | - | 富文本编辑器 |
|
| TinyMce | - | 富文本编辑器 |
|
||||||
| ECharts | - | 数据可视化图表库 |
|
| ECharts | - | 数据可视化图表库 |
|
||||||
| pnpm | - | 高性能 Node.js 包管理器 |
|
| pnpm | - | 高性能 Node.js 包管理器 |
|
||||||
|
|||||||
@@ -74,33 +74,13 @@ await userStore.logout();
|
|||||||
|
|
||||||
| 字段 | 说明 |
|
| 字段 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `navMode` | 导航模式(侧边/顶部) |
|
| `layout` | 布局模式(side 侧边 / top 顶部 / mix 拆分) |
|
||||||
| `navTheme` | 导航主题(暗色/亮色) |
|
| `navTheme` | 导航主题(light / dark) |
|
||||||
| `isShowMenuBar` | 是否显示菜单栏 |
|
| `headerTheme` | 标题栏主题 |
|
||||||
| `isCollapse` | 菜单是否折叠 |
|
| `menuSetting` | 菜单配置(宽度、折叠、固定等) |
|
||||||
|
| `multiTabsSetting` | 多标签页配置 |
|
||||||
---
|
| `permissionMode` | 菜单权限模式(ROLE / BACK) |
|
||||||
|
| `isWaterMark` | 是否显示水印 |
|
||||||
## ossConfigStore
|
|
||||||
|
|
||||||
**文件**:`store/modules/ossConfig.ts`
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `ossConfig` | 云存储配置 |
|
|
||||||
|
|
||||||
路由守卫中初始化,用于文件上传。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## designSettingStore
|
|
||||||
|
|
||||||
**文件**:`store/modules/designSetting.ts`
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `appTheme` | 主题色 |
|
|
||||||
| `darkMode` | 是否暗色模式 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -114,13 +94,29 @@ await userStore.logout();
|
|||||||
|
|
||||||
| Action | 说明 |
|
| Action | 说明 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| `addTab(route)` | 添加标签 |
|
| `addTabs(route)` | 添加标签 |
|
||||||
| `removeTab(route)` | 关闭标签 |
|
| `closeCurrentTab(route)` | 关闭当前标签 |
|
||||||
| `closeOtherTabs(route)` | 关闭其他标签 |
|
| `closeOtherTabs(route, activeKey)` | 关闭其他标签 |
|
||||||
| `closeAllTabs()` | 关闭所有标签 |
|
| `closeAllTabs()` | 关闭所有标签 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## lockscreenStore
|
||||||
|
|
||||||
|
**文件**:`store/modules/lockscreen.ts`
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `isLock` | 是否锁屏 |
|
||||||
|
| `lockTime` | 锁屏剩余时间 |
|
||||||
|
|
||||||
|
| Action | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `setLock(flag)` | 设置锁屏状态(持久化到 Storage) |
|
||||||
|
| `setLockTime(time)` | 设置锁屏时间 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 在组件外使用
|
## 在组件外使用
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ ALTER TABLE think_city ADD INDEX idx_pid (pid);
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql
|
TYPE = mysql
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp8.antdvue
|
||||||
USERNAME = root
|
USERNAME = root
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 3306
|
HOSTPORT = 3306
|
||||||
@@ -304,7 +304,7 @@ HOSTPORT = 3306
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = pgsql
|
TYPE = pgsql
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp8.antdvue
|
||||||
USERNAME = postgres
|
USERNAME = postgres
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 5432
|
HOSTPORT = 5432
|
||||||
@@ -314,7 +314,7 @@ HOSTPORT = 5432
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = sqlsrv
|
TYPE = sqlsrv
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp8.antdvue
|
||||||
USERNAME = sa
|
USERNAME = sa
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 1433
|
HOSTPORT = 1433
|
||||||
@@ -324,7 +324,7 @@ HOSTPORT = 1433
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = oracle
|
TYPE = oracle
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = 127.0.0.1
|
||||||
DATABASE = rxthinkcmf.thinkphp6.elevue
|
DATABASE = rxthinkcmf.thinkphp8.antdvue
|
||||||
USERNAME = root
|
USERNAME = root
|
||||||
PASSWORD = root
|
PASSWORD = root
|
||||||
HOSTPORT = 1521
|
HOSTPORT = 1521
|
||||||
@@ -400,7 +400,7 @@ MySQL 是项目默认推荐的关系型数据库,XAMPP 已自带,开箱即
|
|||||||
# 若前面有分号则去掉,保存后重启 XAMPP
|
# 若前面有分号则去掉,保存后重启 XAMPP
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
D:\xampp8.2\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
D:\xampp8.2\mysql\bin\mysql.exe -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp8.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -409,7 +409,7 @@ brew services start mysql
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp8.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -419,7 +419,7 @@ sudo systemctl enable mysql
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp8.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -429,7 +429,7 @@ sudo systemctl enable mysqld
|
|||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp8.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -454,7 +454,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
psql -U postgres -c "CREATE DATABASE \"rxthinkcmf.thinkphp8.antdvue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -465,8 +465,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp8.antdvue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp8.antdvue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -477,8 +477,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp8.antdvue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp8.antdvue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -490,8 +490,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.thinkphp6.elevue\" OWNER postgres ENCODING 'UTF8';"
|
sudo -u postgres psql -c "CREATE DATABASE \"rxthinkcmf.thinkphp8.antdvue\" OWNER postgres ENCODING 'UTF8';"
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp6.elevue\" TO postgres;"
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"rxthinkcmf.thinkphp8.antdvue\" TO postgres;"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -558,7 +558,7 @@ SQL Server 是微软商业数据库,XAMPP 已自带 pdo_sqlsrv 驱动,Window
|
|||||||
# extension=php_pdo_sqlsrv_82_ts_x64.dll
|
# extension=php_pdo_sqlsrv_82_ts_x64.dll
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp8.antdvue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [macOS]
|
```sh [macOS]
|
||||||
@@ -571,7 +571,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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp8.antdvue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Ubuntu / Debian]
|
```sh [Ubuntu / Debian]
|
||||||
@@ -593,7 +593,7 @@ echo "extension=pdo_sqlsrv.so" | sudo tee /etc/php/8.2/mods-available/pdo_sqlsrv
|
|||||||
sudo phpenmod pdo_sqlsrv
|
sudo phpenmod pdo_sqlsrv
|
||||||
|
|
||||||
# 创建数据库
|
# 创建数据库
|
||||||
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp8.antdvue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh [Rocky Linux / CentOS]
|
```sh [Rocky Linux / CentOS]
|
||||||
@@ -613,7 +613,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.thinkphp6.elevue] COLLATE Chinese_PRC_CI_AS"
|
sqlcmd -S localhost -U sa -P root -Q "CREATE DATABASE [rxthinkcmf.thinkphp8.antdvue] COLLATE Chinese_PRC_CI_AS"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -647,7 +647,7 @@ Oracle 是企业级商业数据库,需手动安装 Instant Client 和 pdo_oci
|
|||||||
# .env 配置
|
# .env 配置
|
||||||
# DATABASE.TYPE = oracle
|
# DATABASE.TYPE = oracle
|
||||||
# DATABASE.HOSTNAME = 127.0.0.1
|
# DATABASE.HOSTNAME = 127.0.0.1
|
||||||
# DATABASE.DATABASE = rxthinkcmf.thinkphp6.elevue
|
# DATABASE.DATABASE = rxthinkcmf.thinkphp8.antdvue
|
||||||
# DATABASE.USERNAME = root
|
# DATABASE.USERNAME = root
|
||||||
# DATABASE.PASSWORD = root
|
# DATABASE.PASSWORD = root
|
||||||
# DATABASE.HOSTPORT = 1521
|
# DATABASE.HOSTPORT = 1521
|
||||||
@@ -679,8 +679,8 @@ sudo phpenmod pdo_oci
|
|||||||
|
|
||||||
# 4. 创建表空间和用户
|
# 4. 创建表空间和用户
|
||||||
sqlplus system/root@localhost:1521/XE <<EOF
|
sqlplus system/root@localhost:1521/XE <<EOF
|
||||||
CREATE TABLESPACE "rxthinkcmf_thinkphp6_elevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_elevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
CREATE TABLESPACE "rxthinkcmf_thinkphp8_antdvue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp8_antdvue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
||||||
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_elevue";
|
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp8_antdvue";
|
||||||
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
||||||
GRANT UNLIMITED TABLESPACE TO "root";
|
GRANT UNLIMITED TABLESPACE TO "root";
|
||||||
EXIT;
|
EXIT;
|
||||||
@@ -703,8 +703,8 @@ sudo phpenmod pdo_oci
|
|||||||
|
|
||||||
# 4. 创建表空间和用户
|
# 4. 创建表空间和用户
|
||||||
sqlplus system/root@localhost:1521/XE <<EOF
|
sqlplus system/root@localhost:1521/XE <<EOF
|
||||||
CREATE TABLESPACE "rxthinkcmf_thinkphp6_elevue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp6_elevue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
CREATE TABLESPACE "rxthinkcmf_thinkphp8_antdvue" DATAFILE '/opt/oracle/oradata/XE/rxthinkcmf_thinkphp8_antdvue.dbf' SIZE 100M AUTOEXTEND ON NEXT 100M;
|
||||||
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp6_elevue";
|
CREATE USER "root" IDENTIFIED BY "root" DEFAULT TABLESPACE "rxthinkcmf_thinkphp8_antdvue";
|
||||||
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
GRANT CONNECT, RESOURCE, CREATE VIEW TO "root";
|
||||||
GRANT UNLIMITED TABLESPACE TO "root";
|
GRANT UNLIMITED TABLESPACE TO "root";
|
||||||
EXIT;
|
EXIT;
|
||||||
@@ -720,15 +720,15 @@ EOF
|
|||||||
```text
|
```text
|
||||||
document/
|
document/
|
||||||
├── mysql/
|
├── mysql/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本
|
│ └── rxthinkcmf.thinkphp8.antdvue.sql # MySQL 版本
|
||||||
├── postgresql/
|
├── postgresql/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
|
│ └── rxthinkcmf.thinkphp8.antdvue.sql # PostgreSQL 版本
|
||||||
├── sqlserver/
|
├── sqlserver/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
|
│ └── rxthinkcmf.thinkphp8.antdvue.sql # SQL Server 版本
|
||||||
├── oracle/
|
├── oracle/
|
||||||
│ └── rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
|
│ └── rxthinkcmf.thinkphp8.antdvue.sql # Oracle 版本
|
||||||
└── sqlite/
|
└── sqlite/
|
||||||
└── rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
|
└── rxthinkcmf.thinkphp8.antdvue.sql # SQLite 版本
|
||||||
```
|
```
|
||||||
|
|
||||||
各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。
|
各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
├── templates/ ← 代码生成器模板
|
├── templates/ ← 代码生成器模板
|
||||||
├── runtime/ ← 运行时缓存(自动生成)
|
├── runtime/ ← 运行时缓存(自动生成)
|
||||||
├── vendor/ ← Composer 依赖
|
├── vendor/ ← Composer 依赖
|
||||||
├── wiki/ ← 文档站点(VitePress)
|
|
||||||
├── document/ ← 开发参考手册(txt)
|
├── document/ ← 开发参考手册(txt)
|
||||||
├── .env ← 环境变量配置
|
├── .env ← 环境变量配置
|
||||||
└── think ← ThinkPHP 命令行入口
|
└── think ← ThinkPHP 命令行入口
|
||||||
|
|||||||