87ece5a971
前端此前只有本地实现、从未入库,本次首次纳入版本管理。
框架切换
· 以 Gi Admin Pro(Vue3 + Vite + TS + Arco Design Vue + Pinia)为底
· 剥离 14 个演示页面目录 / 121 个文件(demo 28、file 20、form 19、system 18…)
· 依赖 dependencies 35 → 19,devDependencies 31 → 27
· 保留框架核心:layout、Gi* 组件库、hooks、权限指令、错误页、登录页
主题:腾讯云 / TDesign 官方色板
· 主色 #0052D9(腾讯蓝)、成功 #00A870、警告 #ED7B2F、危险 #E34D59
· 页面底色 #f3f3f3、文字 rgba(0,0,0,90/60/40/26%)
· 覆盖 Arco 底层色板 + 显式语义别名(primary/success/warning/danger/link/neutral)
· 三个改了不生效也不报错的坑已记入注释:
1) Arco 色值是逗号分隔 RGB 三元组,不是 #RRGGBB
2) Arco 把色板定义在 body 而非 :root,必须写 `:root, body`
3) setting.json 的 themeColor 会以内联样式写进 body,优先级高于样式表
对接后端(三处契约差异在 @/utils/http 统一适配)
· 响应信封 {code,msg,data} → {code,success,message,data}
· Token 头 token → Authorization: Bearer
· 分页 {list,total} → 补一份 records,list 原样保留
· 40101 → 登录页;登录密码 RSA 加密(公钥走 /auth/public-key,轮换无需重建前端)
· 路由:后端只下发菜单键,前端在本地路由表上过滤(有哪些页面归代码,谁能看归后端)
已实现页面(浏览器实测通过)
· 登录:RSA 加密 + 自适应验证码/租户码 + 账号锁定提示
· 工作台:真实数据(组织 6 个 / 角色 1 个 / 权限 25 项)
· 组织架构:三级树 + CRUD + 占用校验
· 租户管理:开通租户 + 初始密码只显示一次的强确认
· 待建页:未实现菜单显示所属里程碑
· 权限菜单:学校管理员看不到「租户管理」
视觉资产按腾讯云主题重做
· logo.svg(渐变圆角方 + 填)、favicon.svg(小尺寸做减法)、login-hero.svg(材料归集)
· 登录页点阵纹理走主题变量,换主色自动跟随
· 文案改为产品实际能力:自动改名 / 强制压缩 / 隐私保护
清理模板遗留(约 17MB + 三处外部依赖)
· 百度统计脚本(上报到 gi-demo 作者的统计账号)
· 用户菜单「项目地址」(跳转模板作者的 Gitee 仓库)
· 两份 8.8MB mp3、388KB favicon.ico、57KB 模板插画、未引用字体与 loading.gif
· 「个人中心」指向已删除的 /system/account(点了 404)
· 头部头像在无 avatarUrl 时渲染成裂图 → 改为姓名首字兜底
后端修复
· 新增 GET /auth/public-key(前端加密登录密码用)
· DataSeeder.ancestor_path 生成错误:子节点本该是 /16/17/ 却写成 /17/
该字段是「按院系统计」的索引,错了会漏人/多人且不报错 —— 改为从库里读父节点路径
· seedDemoOrgs 曾放在租户作用域外 + admin 早退分支之后,导致启动失败 / 永不执行
· 示范租户补一套三级组织树(1 学校 / 2 院系 / 3 班级),供界面演示
623 lines
17 KiB
Markdown
623 lines
17 KiB
Markdown
<div align="center">
|
||
<img style="width: 80px;height: 80px" src="https://gitee.com/lin0716/gi-image/raw/master/gi-logo.gif"/>
|
||
<h1>Gi Admin Pro</h1>
|
||
<a href='https://gitee.com/lin0716/gi-demo/stargazers'><img src='https://gitee.com/lin0716/gi-demo/badge/star.svg?theme=dark' alt='star'></img></a>
|
||
</div>
|
||
[](./LICENSE)
|
||
|
||
## 简介
|
||
|
||
**Gi Admin Pro** 是一个基于 Vue3、Vite、TypeScript、Arco Design Vue、Pinia、VueUse 等的免费中后台模版,它使用了最新的前端技术栈,内置丰富的主题配置,有着极高的代码规范,基于 mock 实现的动态数据展示,开箱即用的模板,也可用于学习参考。
|
||
|
||
> **Gi 前缀含义:** G:代表全局 i:代表我的
|
||
>
|
||
> Gi 用来定义全局组件前缀,如 GiTable、GiForm、GiPageLayout等
|
||
|
||
## 特性
|
||
|
||
- **最新技术栈**:使用 Vue3 / Vite 等前端前沿技术开发,使用高效率的 npm 包管理器
|
||
- **TypeScript**:应用程序级 JavaScript 的语言
|
||
- **主题**:丰富可配置的主题、暗黑模式
|
||
- **代码规范**:丰富的规范插件及极高的代码规范
|
||
|
||
## 预览
|
||
|
||
|
||
| 平台 | 预览地址 |
|
||
| ---------- | ---------------------------------------------------------- |
|
||
| **github** | [Gi Admin Pro 预览地址](https://lin-97.github.io/gi-demo/) |
|
||
|
||
|
||
| | 账号 | 密码 |
|
||
| ------ | ----- | ------ |
|
||
| 管理员 | admin | 123456 |
|
||
| 用户 | user | 123456 |
|
||
|
||
## 代码仓库
|
||
|
||
|
||
| 平台 | 预览地址 | 仓库地址 |
|
||
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||
| **gitee**(码云) | -- | <a href="https://gitee.com/lin0716/gi-demo" target="_blank">Gitee 仓库地址</a> |
|
||
| **github** | <a href="https://lin-97.github.io/gi-demo/" target="_blank">Gi Admin Pro 预览地址</a> | <a href="https://github.com/lin-97/gi-demo" target="_blank">Github 仓库地址</a> |
|
||
|
||
## 项目示例图
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/01.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/02.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/03.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/04.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/05.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/06.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/07.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/08.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/09.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/10.png" />
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/demo/11.png" />
|
||
|
||
## 安装使用
|
||
|
||
- 安装依赖
|
||
|
||
```bash
|
||
npm install
|
||
```
|
||
|
||
- 运行
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
- 打包
|
||
|
||
```bash
|
||
npm run build
|
||
```
|
||
|
||
## 开源作者
|
||
|
||
**Lin**
|
||
|
||
## 常见问题
|
||
|
||
**为什么安装依赖不成功?**
|
||
|
||
检查`node`版本,最好使用原生镜像`npm`
|
||
|
||
还原镜像
|
||
|
||
```
|
||
npm config set registry https://registry.npmjs.org/
|
||
```
|
||
|
||
**为什么选择 Arco 组件库,而不是 Element Plus?**
|
||
|
||
[Element Plus 对比 Arco design](https://juejin.cn/post/7294219581894705190)
|
||
|
||
**为什么全局组件使用前缀 Gi?**
|
||
|
||
全局组件设置了按需引入,使用前缀,方便和局部组件做区分
|
||
|
||
**为什么组件使用单词大写开头 (PascalCase)命名写法?**
|
||
|
||
本项目`.vue`文件名以及在模板使用中,均采用大写`开头 (PascalCase)`命名方式
|
||
|
||
参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/
|
||
|
||
组件命名:`单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)`
|
||
|
||
其他优点:方便搜索(横线连接 (kebab-case)对搜索没那么方便)
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/comp-name.png" />
|
||
|
||
**为什么 css 类名推荐横线连接 (kebab-case)**
|
||
|
||
参考大部分大网站,都是这个命名规则,不使用: `.myClass`这种
|
||
|
||
**页面显示异常?**
|
||
|
||
**`页面必须要保留一个根元素!!!`**
|
||
|
||
**Vue3 权限管理对路由进行排序和格式化处理方式**
|
||
|
||
使用 **xe-utils** 这个 js 库,简化数据处理
|
||
|
||
[文章地址](https://juejin.cn/post/7301260557222805567)
|
||
|
||
**页面无法缓存?**
|
||
|
||
请检查页面是否配置了`name`,且名称是否与数据一致
|
||
|
||
```js
|
||
defineOptions({ name: 'AboutIndex' })
|
||
```
|
||
|
||
```js
|
||
{
|
||
path: '/about/index',
|
||
name: 'AboutIndex', // 检查name是否一致
|
||
component: () => import('@/views/about/index.vue')
|
||
}
|
||
```
|
||
|
||
## 项目规范
|
||
|
||
#### .vue 文件行数规范
|
||
|
||
一般来说,一个 .vue 文件行数建议不超过 **`400`** 行,超过建议组件化拆分
|
||
|
||
#### vue 相关的命名
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
const isEdit = ref(false)
|
||
|
||
// 不推荐
|
||
const title = computed(() => {
|
||
return isEdit.value ? '编辑' : '新增'
|
||
})
|
||
|
||
// 推荐 能一行就尽量写一行
|
||
const title = computed(() => (isEdit.value ? '编辑' : '新增'))
|
||
</script>
|
||
```
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
// 表单建议使用 form 命名(简洁),不必要使用 formData, 同时使用 reactive
|
||
const form = reactive({
|
||
name: '',
|
||
phone: ''
|
||
})
|
||
</script>
|
||
```
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
// 如果属性比较多
|
||
const getInitForm = () => ({
|
||
name: '',
|
||
phone: '',
|
||
email: '',
|
||
sex: 1,
|
||
age: ''
|
||
})
|
||
|
||
const form = reactive(getInitForm())
|
||
|
||
// 重置form
|
||
const resetForm = () => {
|
||
for (const key in form) {
|
||
delete form[key]
|
||
}
|
||
Object.assign(form, getInitForm())
|
||
}
|
||
</script>
|
||
```
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { useAppStore, useUserStore } from '@/stores'
|
||
import { useLoading } from '@/hooks'
|
||
|
||
// stores 或 hooks 的使用命名规则定义
|
||
const appStore = useAppStore()
|
||
const userStore = useUserStore()
|
||
|
||
const { loading, setLoading } = useLoading()
|
||
</script>
|
||
```
|
||
|
||
#### 写法技巧
|
||
|
||
尽量使用三元表达式
|
||
|
||
```js
|
||
// 优化前
|
||
let isEdit = true
|
||
let title = ''
|
||
if (isEdit) {
|
||
title = '编辑'
|
||
} else {
|
||
title = '新增'
|
||
}
|
||
|
||
// 优化后
|
||
let title = isEdit ? '编辑' : '新增'
|
||
```
|
||
|
||
善用 includes 方法
|
||
|
||
```js
|
||
// 优化前
|
||
if (type === 1 || type === 2 || type === 3) {
|
||
}
|
||
|
||
// 优化后,此种方式在vue模板也可使用
|
||
if ([1, 2, 3].includes(type)) {
|
||
}
|
||
```
|
||
|
||
使用箭头函数简化函数
|
||
|
||
```js
|
||
// 优化前
|
||
function add(num1, num2) {
|
||
return num1 + num2
|
||
}
|
||
|
||
// 优化后
|
||
const add = (num1, num2) => num1 + num2
|
||
```
|
||
|
||
尽量减少 if else if
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
// 比例进度条颜色 尽量减少 if else if
|
||
const getProportionColor = (proportion: number) => {
|
||
if (proportion < 30) return 'danger'
|
||
if (proportion < 60) return 'warning'
|
||
return 'success'
|
||
}
|
||
</script>
|
||
```
|
||
|
||
```js
|
||
// 优化前
|
||
const status = 200
|
||
const message = ''
|
||
if (status === 200) {
|
||
message = '请求成功'
|
||
} else if (status === 404) {
|
||
message = '请求出错'
|
||
} else if (status === 500) {
|
||
message = '服务器错误'
|
||
}
|
||
|
||
// 优化后
|
||
const status = 200
|
||
const messageMap = {
|
||
200: '请求成功',
|
||
404: '请求出错',
|
||
500: '服务器错误'
|
||
}
|
||
const message = messageMap[status]
|
||
```
|
||
|
||
如果函数参数超过两个,建议优化
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
function createUser(name, phone, age) {
|
||
console.log('姓名', name)
|
||
console.log('手机', phone)
|
||
console.log('年龄', age)
|
||
}
|
||
|
||
// 这种方式在使用的时候可读性很差,扩展性差,而且不易于维护
|
||
createUser('张三', '178****2828', 20)
|
||
|
||
function createUser2({ name, phone, age }) {
|
||
console.log('姓名', name)
|
||
console.log('手机', phone)
|
||
console.log('年龄', age)
|
||
}
|
||
|
||
// 以对象传参更直观,更好扩展和维护
|
||
createUser2({ name: '张三', phone: '178****2828', age: 20 })
|
||
</script>
|
||
```
|
||
|
||
#### 接口调用书写
|
||
|
||
**写法一**
|
||
|
||
不需要 loading,不需要错误打印的 情况
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { getUserList as getUserListApi, type UserItem } from '@/apis' // 同名可以使用别名
|
||
|
||
const userList = ref<UserItem[]>([])
|
||
const getUserList = async () => {
|
||
const res = await getUserListApi()
|
||
console.log('如果异步成功,则会打印这行文字,否则不会打印这行文字,也不会往下执行')
|
||
userList.value = res.data
|
||
}
|
||
</script>
|
||
|
||
// getUserListApi 是一个 Promise 异步函数,Promise 最后只有 成功 / 失败 两种状态 // getUserListApi 是基于 axios
|
||
封装的,在 axios 响应拦截器做了处理 // 当 res.success === false 的时候 Promise.reject(),
|
||
也就异步失败,异步失败不会往下执行
|
||
```
|
||
|
||
**写法二**
|
||
|
||
需要 loading,需要错误打印的情况
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { getUserList as getUserListApi, type UserItem } from '@/apis' // 同名可以使用别名
|
||
|
||
const loading = ref(false)
|
||
const userList = ref<UserItem[]>([])
|
||
const getUserList = async () => {
|
||
try {
|
||
loading.value = true
|
||
const res = await getUserListApi()
|
||
console.log('如果异步成功,则会打印这行文字,否则不会打印这行文字,也不会往下执行')
|
||
userList.value = res.data
|
||
} catch (error) {
|
||
console.log('如果异步失败,会打印这行文字')
|
||
// 由于 axios 在封装的时候,已经在异步失败给了失败的提示弹窗处理
|
||
// 所以这里没必要再写 Message.error(error)
|
||
} finally {
|
||
console.log('如果异步或者失败,都会打印这行代码')
|
||
loading.value = false // 可以用来处理 loading
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
**写法三**
|
||
|
||
需要 loading, 不需要错误打印(不进行错误处理)的情况
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { getUserList as getUserListApi, type UserItem } from '@/apis' // 同名可以使用别名
|
||
|
||
const loading = ref(false)
|
||
const userList = ref<UserItem[]>([])
|
||
const getUserList = async () => {
|
||
try {
|
||
loading.value = true
|
||
const res = await getUserListApi()
|
||
console.log('如果异步成功,则会打印这行文字,否则不会打印这行文字,也不会往下执行')
|
||
userList.value = res.data
|
||
} finally {
|
||
console.log('如果异步或者失败,都会打印这行代码')
|
||
loading.value = false // 可以用来处理 loading
|
||
}
|
||
}
|
||
</script>
|
||
|
||
// catch 可以省略
|
||
```
|
||
|
||
#### 全局组件--命名规范
|
||
|
||
组件命名:**单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)**
|
||
|
||
可参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/
|
||
|
||
```
|
||
GiForm.vue
|
||
GiTable.vue
|
||
GiPageLayout.vue
|
||
```
|
||
|
||
#### 局部组件--命名规范
|
||
|
||
组件命名:**单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)**
|
||
|
||
可参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/
|
||
|
||
```
|
||
Pane1.vue
|
||
Pane2.vue
|
||
PaneQuota1.vue
|
||
PaneQuota2.vue
|
||
Step1.vue
|
||
Step2.vue
|
||
AddModal.vue
|
||
EditDrawer.vue
|
||
DetailModal.vue
|
||
```
|
||
|
||
#### 文件夹命名--命名规范 (采用中划线-)
|
||
|
||
1、文件名建议只使用小写字母,不使用大写字母
|
||
|
||
2、名称较长时采用半角连接符(-)分隔
|
||
|
||
```
|
||
home/index.vue
|
||
user/index.vue
|
||
user-detail/index.vue
|
||
```
|
||
|
||
#### 弹窗组件 Modal、抽屉组件 Drawer 的一般封装
|
||
|
||
```vue
|
||
<template>
|
||
<a-modal v-model:visible="visible" :title="title" @ok="confirm">
|
||
<!-- 内容 -->
|
||
</a-modal>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, reactive, ref } from 'vue'
|
||
|
||
const visible = ref(false)
|
||
const detailId = ref('')
|
||
const isEdit = computed(() => !!detailId.value) // 判断是新增还是编辑模式
|
||
const title = computed(() => (isEdit.value ? '编辑' : '新增'))
|
||
|
||
const add = () => {
|
||
detailId.value = ''
|
||
visible.value = true
|
||
}
|
||
|
||
// 如果这里的参数超过两个,建议优化成对象形式
|
||
// const edit = ({ id, taskId }) = {
|
||
// console.log(id, taskId)
|
||
// }
|
||
|
||
const edit = (id: string) => {
|
||
detailId.value = id
|
||
visible.value = true
|
||
// getDetail() 回显操作
|
||
}
|
||
|
||
defineExpose({ add, edit })
|
||
|
||
const confirm = () => {
|
||
console.log('点击了确认按钮')
|
||
}
|
||
</script>
|
||
```
|
||
|
||
使用
|
||
|
||
**`模板里使用自定义组件:使用大写开头驼峰,双击好复制,对于搜索便利`**
|
||
|
||
```vue
|
||
<template>
|
||
<EditModal ref="EditModalRef"></EditModal>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import EditModal from './EditModal.vue'
|
||
|
||
const EditModalRef = ref<InstanceType<typeof EditModal>>()
|
||
|
||
// 新增
|
||
const onAdd = () => {
|
||
EditModalRef.value?.add()
|
||
}
|
||
|
||
// 编辑
|
||
const onEdit = (item: PersonItem) => {
|
||
EditModalRef.value?.edit(item.id)
|
||
}
|
||
</script>
|
||
```
|
||
|
||
#### CSS 命名规范
|
||
|
||
建议采用全小写,多单词使用-连接符(参考大部分网站,包括掘金,码云等,都是采用这个规则)
|
||
|
||
或者采用`BEM`命名规范 [BEM 命名规范](https://getbem.com/naming/)
|
||
|
||
**BEM 命名规范**
|
||
|
||
```html
|
||
<div class="article">
|
||
<div class="article__body">
|
||
<button class="article__button--primary"></button>
|
||
<button class="article__button--success"></button>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
|
||
其他:
|
||
|
||
位置 1: 使用 var(--margin) 全局 css 变量
|
||
|
||
位置 2:使用 var(--padding) 全局 css 变量
|
||
|
||
建议尽量使用全局 css 变量来开发,可以有效提高效率和团队协作
|
||
|
||
<img src="https://gitee.com/lin0716/gi-image/raw/master/gap.png" />
|
||
|
||
|
||
|
||
#### 其他规范
|
||
|
||
可参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/ , 其中一些规范也可借鉴
|
||
|
||
可参考 **Gi Admin Pro** 源码,如有更好的规范建议,可以联系作者本人
|
||
|
||
## Vue 相关
|
||
|
||
<a href="https://cn.vuejs.org/" target="_blank">Vue3 官网</a>
|
||
|
||
<a href="https://router.vuejs.org/zh/" target="_blank">Vue-Router</a>
|
||
|
||
<a href="https://cn.vitejs.dev/" target="_blank">Vite</a>
|
||
|
||
<a href="https://pinia.web3doc.top/" target="_blank">Pinia</a>
|
||
|
||
## 插件推荐
|
||
|
||
<a href="https://arco.design/vue/component/button" target="_blank">Arco Design 组件库</a>
|
||
|
||
<a href="https://dayjs.fenxianglu.cn/" target="_blank">Day.js 一个极简的 JavaScript 库,可以为现代浏览器解析、验证、操作和显示日期和时间 2K 大小</a>
|
||
|
||
<a href="https://www.lodashjs.com/" target="_blank">Lodash 一个一致性、模块化、高性能的 JavaScript 实用工具库</a>
|
||
|
||
<a href="https://vxetable.cn/xe-utils/#/" target="_blank">Xe-utils 一个 JavaScript 函数库、工具类</a>
|
||
|
||
<a href="https://vueuse.org/" target="_blank">VueUse 一个 Vue3 Hooks 库</a>
|
||
|
||
<a href="https://next.attojs.com/" target="_blank">VueRequest 一个 Vue 请求库</a>
|
||
|
||
<a href="https://mirari.cc/v-viewer/" target="_blank">V-Viewer 基于 viewer.js 的 vue 图像查看器组件,支持旋转、缩放等操作</a>
|
||
|
||
<a href="https://www.npmjs.com/package/vue-color-kit" target="_blank">Vue-Color-Kit 一个 vue3 颜色选择器组件</a>
|
||
|
||
<a href="https://vxetable.cn/#/table/start/install" target="_blank">Vxe-Table</a>
|
||
|
||
<a href="https://github.com/fslflame/vue3-request" target="_blank">vue3-request 一个专为 Vue 3 设计的异步请求处理库,它通过统一的 useRequest Hook 和强大的插件生态,彻底简化了异步操作和 API 调用的复杂性</a>
|
||
|
||
**其他**
|
||
|
||
<a href="https://vcalendar.io/" target="_blank">VCalendar 日历组件</a>
|
||
|
||
<a href="https://antoniandre.github.io/vue-cal/" target="_blank">Vue Cal 日历组件</a>
|
||
|
||
<a href="https://alfred-skyblue.github.io/vue-draggable-plus/" target="_blank">VueDraggablePlus 支持 Vue2 和 Vue3 的拖拽组件</a>
|
||
|
||
## 推荐书籍
|
||
|
||
<a href="https://vue3.chengpeiquan.com/" target="_blank">Vue 入门指南与实战案例</a>
|
||
|
||
<a href="https://jkchao.github.io/typescript-book-chinese/" target="_blank">深入理解 TypeScript</a>
|
||
|
||
<a href="https://vue3js.cn/interview/" target="_blank">前端宝典</a>
|
||
|
||
<a href="https://lhammer.cn/You-need-to-know-css/#/translucent-borders" target="_blank">Web 开发人员需要知道的 CSS 技巧</a>
|
||
|
||
<a href="https://es6.ruanyifeng.com/#README" target="_blank">阮一峰 ES6</a>
|
||
|
||
<a href="https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html" target="_blank">阮一峰 flex 布局</a>
|
||
|
||
## 开源项目集合
|
||
|
||
<a href="https://vue-admin.cn/admin" target="_blank">Vue3 开源项目集合</a>
|
||
|
||
<a href="https://react-admin.cn/admin/" target="_blank">React 开源项目集合</a>
|
||
|
||
## 工具
|
||
|
||
<a href="https://c.runoob.com/" target="_blank">菜鸟工具</a>
|
||
|
||
<a href="https://carbon.now.sh/?bg=rgba%28171%2C+184%2C+195%2C+1%29&t=material&wt=none&l=auto&width=680&ds=true&dsyoff=20px&dsblur=68px&wc=true&wa=true&pv=56px&ph=56px&ln=false&fl=1&fm=Hack&fs=14px&lh=133%25&si=false&es=2x&wm=false" target="_blank">代码生成图片</a>
|
||
|
||
<a href="http://xiets.gitee.io/json-to-any-web/" target="_blank">JSON-TO-ANY 对象转 ts 类型</a>
|
||
|
||
<a href="http://49.234.61.19/tool/cssTriangle" target="_blank">在线三角形样式生成器</a>
|
||
|
||
<a href="https://go.itab.link/" target="_blank">iTab</a>
|
||
|
||
## 捐赠
|
||
|
||
<img style="width:300px" src="https://gitee.com/lin0716/gi-image/raw/master/alipay.jpg" />
|