手摸手,带你尝鲜 naiveui 撸 admin 骨架(准备篇)
ccwgpt 2024-10-04 13:52 128 浏览 0 评论
前言
最近admin骨架,呼声比较多,说好的教程呢,咋也不能做个失言的小伙子,言归正传,第一篇文章主要来说一说在开始写实际业务代码之前的一些准备工作。
目录结构
├── build // 构建相关
├── mock // 项目mock 模拟数据
├── types // 全局 ts 类型 接口定义
├── config // 配置相关
├── src // 源代码
│ ├── api // 所有请求
│ ├── assets // 主题 字体等静态资源
│ ├── components // 全局公用组件
│ ├── directive // 全局指令
│ ├── enums // 枚举定义
│ ├── hooks // 公共hooks
│ ├── layout //骨架布局
│ ├── plugins //第三方插件
│ ├── router //路由配置
│ ├── settings //配置相关
│ ├── store //状态管理器
│ ├── styles //公共样式
│ ├── utils // 全局公用方法
│ ├── views // 页面
│ ├── App.vue // 入口页面
│ ├── main.ts // 入口 加载组件 初始化等
├── .gitignore // git 忽略项
├── .eslintignore // eslint 忽略项
├── .eslintrc.js // eslint 配置
├── tailwind.config.js // tailwind 配置
├── stylelint.config.js // stylelint 配置
├── postcss.config.js // postcss 配置
├── prettier.config.js // prettier 配置
├── tsconfig.json // ts 配置
├── vite.config.js // vite 配置
├── index.html // html模板
└── package.json // package.json
└── 省略........ // 更多介绍的就不写了哈,然后,没有然后了
api 和 views
目前定义如下,随着页面越来越多,建议还是,api 模块和views对应起来,方便后期维护。
components
这里的 components 放置的都是全局公用的一些组件,如上传组件,表格,弹窗,等等。一些页面级的组件建议还是放在各自views文件下,方便管理。如图:
store
这里我个人建议用 pinia 代替 vuex,也可以说是习惯吧,看你怎么选了,我个人感觉 pinia 优雅一点,具体优点不过多陈述,可移步官网瞧瞧哈。
vite
下一代前端开发与构建工具,极速的服务启动,轻量快速地热重载,优化的构建,完全类型化的API,更多优点也不过多陈述,可移步官网瞧瞧哈。
这里说一下,alias,和 plugins
alias,当项目逐渐变大之后,文件与文件直接的引用关系会很复杂,这时候就需要使用alias 了, 有的人喜欢alias 指向src目录下,再使用相对路径找文件,因人而异了。
plugins,当项目逐渐变大之后,vite.config.js 可能会变得又臭又长,尤其是 plugins 所以建议还是,抽离出来,比如下面这样:
ESLint
不管是多人合作还是个人项目,代码规范是很重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性,个人推荐 eslint+vscode | webstorm 来写 vue,绝对有种飞一般的感觉。
mock
vite项目推荐用,vite-plugin-mock 来构建mock,使用简单,以下这种写法,只需在mock中创建文件,和api里面的接口对应起来,即可实现mock
封装 axios
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
import { VAxios } from './Axios';
import { AxiosTransform } from './axiosTransform';
import axios, { AxiosResponse } from 'axios';
import { checkStatus } from './checkStatus';
import { joinTimestamp, formatRequestDate } from './helper';
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum';
import { useGlobSetting } from '@/hooks/setting';
import { isString } from '@/utils/is/';
import { setObjToUrlParams } from '@/utils/urlUtils';
import { RequestOptions, Result } from './types';
import { useUserStoreWidthOut } from '@/store/modules/user';
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix || '';
import router from '@/router';
import { storage } from '@/utils/Storage';
/**
* @description: 数据处理,方便区分多种处理方式
*/
const transform: AxiosTransform = {
/**
* @description: 处理请求数据
*/
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
const { $message: Message, $dialog: Modal } = window;
const {
isShowMessage = true,
isShowErrorMessage,
isShowSuccessMessage,
successMessageText,
errorMessageText,
isTransformResponse,
isReturnNativeResponse,
} = options;
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse) {
return res;
}
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
if (!isTransformResponse) {
return res.data;
}
const reject = Promise.reject;
const { data } = res;
if (!data) {
// return '[HTTP] Request has no return value';
return reject(data);
}
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
const { code, result, message } = data;
// 请求成功
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
// 是否显示提示信息
if (isShowMessage) {
if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
// 是否显示自定义信息提示
Message.success(successMessageText || message || '操作成功!');
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
// 是否显示自定义信息提示
Message.error(message || errorMessageText || '操作失败!');
} else if (!hasSuccess && options.errorMessageMode === 'modal') {
// errorMessageMode=‘custom-modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
Modal.info({
title: '提示',
content: message,
positiveText: '确定',
onPositiveClick: () => {},
});
}
}
// 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS) {
return result;
}
// 接口请求错误,统一提示错误信息
if (code === ResultEnum.ERROR) {
if (message) {
Message.error(data.message);
Promise.reject(new Error(message));
} else {
const msg = '操作失败,系统异常!';
Message.error(msg);
Promise.reject(new Error(msg));
}
return reject();
}
// 登录超时
if (code === ResultEnum.TIMEOUT) {
if (router.currentRoute.value.name == 'login') return;
// 到登录页
const timeoutMsg = '登录超时,请重新登录!';
Modal.warning({
title: '提示',
content: '登录身份已失效,请重新登录!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
storage.clear();
router.replace({
name: 'login',
query: {
redirect: router.currentRoute.value.fullPath,
},
});
},
onNegativeClick: () => {},
});
return reject(new Error(timeoutMsg));
}
// 这里逻辑可以根据项目进行修改
if (!hasSuccess) {
return reject(new Error(message));
}
return data;
},
// 请求之前处理config
beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options;
if (joinPrefix) {
config.url = `${urlPrefix}${config.url}`;
}
if (apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`;
}
const params = config.params || {};
if (config.method?.toUpperCase() === RequestEnum.GET) {
if (!isString(params)) {
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
} else {
// 兼容restful风格
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
config.params = undefined;
}
} else {
if (!isString(params)) {
formatDate && formatRequestDate(params);
config.data = params;
config.params = undefined;
if (joinParamsToUrl) {
config.url = setObjToUrlParams(config.url as string, config.data);
}
} else {
// 兼容restful风格
config.url = config.url + params;
config.params = undefined;
}
}
return config;
},
/**
* @description: 请求拦截器处理
*/
requestInterceptors: (config) => {
// 请求之前处理config
const userStore = useUserStoreWidthOut();
const token = userStore.getToken;
if (token) {
// jwt token
config.headers.token = token;
}
return config;
},
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: (error: any) => {
const { $message: Message, $dialog: Modal } = window;
const { response, code, message } = error || {};
// TODO 此处要根据后端接口返回格式修改
const msg: string =
response && response.data && response.data.message ? response.data.message : '';
const err: string = error.toString();
try {
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
Message.error('接口请求超时,请刷新页面重试!');
return;
}
if (err && err.includes('Network Error')) {
Modal.info({
title: '网络异常',
content: '请检查您的网络连接是否正常!',
positiveText: '确定',
onPositiveClick: () => {},
});
return;
}
} catch (error) {
throw new Error(error);
}
// 请求是否被取消
const isCancel = axios.isCancel(error);
if (!isCancel) {
checkStatus(error.response && error.response.status, msg, Message);
} else {
console.warn(error, '请求被取消!');
}
return error;
},
};
const Axios = new VAxios({
timeout: 10 * 1000,
// 接口前缀
prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 接口地址
apiUrl: globSetting.apiUrl as string,
},
withCredentials: false,
});
export default Axios;
跨域问题
首先前后端交互不可避免的就会遇到跨域问题,有些是后端小哥解决好了,如果你司后端小哥嫌麻烦不肯配置的话, 可以通过vite的proxy来解决。
提示
此系列教程,持续更新中,直到搭建完整个 Admin Pro 框架,请大家监督我哈~[谢谢]
结尾
以上是一些基础建设工作,下一篇,正式开撸,好了,你说你都看到这了,免费的关注点一点在走吧,好人一生平安,bug都会远离你O(∩_∩)O哈哈~[奸笑]
相关推荐
- 火电厂智能管控新基建:全场景人员定位系统架构解析
-
在能源生产领域,火电厂以庞大的厂区规模、复杂的作业环境和密集的人机交互著称。从高温高压的锅炉房到精密复杂的电气设备间,从露天煤场到灰渣处理区,传统管理模式下的人员定位盲区,正成为制约安全生产与高效运营...
- 安全仪表系统(SIS)全生命周期管理:从设计到运维的深度解析
-
以下是一篇关于安全仪表系统(SIS)的技术解析与实践方法,涵盖系统架构、设计标准、实施流程及行业应用。安全仪表系统(SIS)是工业过程安全的最后一道防线,通过独立于基础控制系统的硬件和逻辑,在工艺失控...
- 数字化转型架构下的数据安全治理方案
-
这份PPT文件内容围绕数字化转型架构下的数据安全治理方案展开,主要探讨了数据质量治理、安全治理、全生命周期治理以及治理考核等方面的内容。更多参考及文档获取详见公众号:优享智库数据治理概述定义与目标:数...
- 安全完整性等级(SIL)分析报告编制与认证实践方法
-
以下是一篇关于安全完整性等级(SIL)分析报告的文章,涵盖SIL定级方法、验证流程、计算模型及工程实践。安全完整性等级(SIL)是量化安全仪表系统(SIS)性能的核心指标,由IEC61508/615...
- Pokemon go下载教程 口袋妖怪Go下载解锁方法
-
#p#安卓下载#e#Pokemongo怎么下载?口袋妖怪go下载教程讲解。虽然锁区了但是大家还是有办法的,首先是口袋妖怪Go下载问题,很多口袋妖怪go的安卓玩家不知道怎么下载游戏,小编给大家详细解答...
- 抛弃Windows吧!谷歌推免费Chrome系统,一个U盘就搞定
-
在目前的个人电脑上,最主流的系统当然是Windows,不过除了Windows之外,我们也可以选择购买苹果的电脑,使用苹果的MacOS系统。不过除了苹果和微软的系统之外,实际上谷歌也有自己用于个人电脑...
- 谷歌误发ARM版Chrome安装包,致英特尔 /AMD用户无法安装
-
IT之家3月26日消息,科技媒体WindowsLatest昨日(3月25日)发布博文,报道称由于谷歌误发适用于ARM架构的安装包,导致用户从谷歌官网下载的ChromeSetu...
- 【Google Gemini极简教程】使用Flask和Gemini API构建一个AI BaaS
-
BaaS(BackendasaService,后端即服务)BaaS是一种云服务模型,它为开发者提供了一种便捷的方式来构建和管理应用程序的后端服务。BaaS提供了一系列的后端功能,如数据库管理...
- 第一资讯Windows 10 Mobile已成功安装谷歌Play Store
-
虽然微软并未正式推出WP可安装APK的功能,但近期有不少玩机爱好者已经在Windows10Mobile平台上用上了Android应用,而且随后还在需要谷歌服务框架(GoogleServices)...
- 宝可梦TCG Pocket谷歌账号登录,给你提供指南
-
《宝可梦TCGPocket》作为一款备受欢迎的卡牌对战游戏,为了给玩家提供更加便捷和安全的登录方式,支持使用谷歌账号进行登录。通过谷歌账号登录,您不仅可以快速进入游戏,还能享受账号数据同步、跨设备游...
- 秒变万能家庭服务器!斐讯N1 armbian安装指南
-
一直以来,我都想配置一台小型服务器放在家里玩一玩,但是x86架构的主机体积大功耗高,价格也不低。而树莓派的话,价格便宜一点,性能对于轻度使用也基本够用。可是树莓派仍然要两三百块钱,感觉还是有点贵。于是...
- 如何自己开发一个Google浏览器插件?
-
相信很多人都好奇,谷歌浏览器那么多的插件是如何开发的,我们如何开发一个自定义的Google浏览器插件,下面我们就来详细的给出一个开发Google浏览器插件的流程。准备环境首先需要有一个文本编辑器工具如...
- 我的世界手机版谷歌商店安装教程(我的世界谷歌下载)
-
在我的世界手机版升级到0.11.0版本后,很多玩家发现正式版需要有谷歌商店的验证也就是必须安装Googleplay才能玩。这次搞趣网小编就为大家带来我的世界手机版谷歌商店安装教程。有些手机自带Goo...
- 部落冲突安卓版谷歌怎么绑定 有无root都可以
-
部落冲突安卓版谷歌怎么绑定?下面小编为大家带来部落冲突安卓版谷歌绑定攻略详解,希望这篇攻略详解能够对大家有所帮助。首先准备这些东西(必须在网上自己下载,不要相信google应用)还需要下载"谷歌服务框...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 火电厂智能管控新基建:全场景人员定位系统架构解析
- 安全仪表系统(SIS)全生命周期管理:从设计到运维的深度解析
- 数字化转型架构下的数据安全治理方案
- 安全完整性等级(SIL)分析报告编制与认证实践方法
- 项目管理体系框架(项目的管理体系)
- Pokemon go下载教程 口袋妖怪Go下载解锁方法
- 抛弃Windows吧!谷歌推免费Chrome系统,一个U盘就搞定
- 谷歌误发ARM版Chrome安装包,致英特尔 /AMD用户无法安装
- 【Google Gemini极简教程】使用Flask和Gemini API构建一个AI BaaS
- 第一资讯Windows 10 Mobile已成功安装谷歌Play Store
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)