# 前端手册
# 开发规范
# 新增 view
在 @/src/pages (opens new window) 文件下 创建对应的模块文件,一般一个路由对应一个文件, 该模块下的功能就建议在本文件夹下创建一个新文件夹,各个功能模块维护自己的api
、utils
或components
组件。
# 新增组件
在全局的 @/src/components (opens new window) 写一些全局的组件,如富文本,各种搜索组件,封装的分页组件等等能被公用的组件。
每个页面或者模块特定的业务组件则会写在当前 @/src/pages/xxx (opens new window) 下面。
如:@/src/pages/system/user/components/xxx.tsx
。这样拆分大大减轻了维护成本。
# 新增样式
页面的样式和组件是一个道理,全局的 @/src/global.less (opens new window) 放置一下全局公用的样式,每一个页面的样式就写在当前 views
下面,请记住加上scoped
就只会作用在当前组件内了,避免造成全局的样式污染。
@import '~antd/es/style/themes/default.less';
// 兼容IE11
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
body .ant-design-pro > .ant-layout {
min-height: 100vh;
}
}
// 修改antd Table默认为居中(依然可以使用columns的config进行配置)
.ant-table-content .ant-table-thead > tr > th, .ant-table-cell {text-align: center}
a:hover{
color: #588fc1;
}
# 请求流程
# 交互流程
一个完整的前端 UI 交互到服务端处理流程是这样的:
- UI 组件交互操作;
- 调用 api service 请求函数;
- 使用封装的 request.js 发送请求;
- 获取服务端返回;
- 更新 data;
提示
其中,@/src/utils/request.js (opens new window) 是基于 axios 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。 它封装了全局 request拦截器、response拦截器、统一的错误处理、统一做了超时处理、baseURL设置等。
# 请求示例
import request, { download, upload } from '@/utils/request'
// 请求的控制器名称
const controller = "/system/user";
/**
* 新增职员
* @param params 搜索参数
*/
export async function insert(params: { [key: string]: any }) {
return request(`${controller}`, {
method: 'POST',
data: {
...params
},
});
}
// /src/pages/human/staff/server.ts
import { insert } from './service'
/**
* 保存数据
*/
const onSave = () => {
form
.validateFields()
.then(
async (fields) => {
try {
// 开启加载中
setLoadingModal(true)
// ID为0则insert,否则将update
const { code, msg } = fields.userId == 0 ? await insert(fields) : await update(fields)
if (code != 200) {
return message.error(msg)
}
message.success(msg)
if (acForm.current) {
acForm.current.reload()
}
// 关闭模态框
handleCancel()
} catch (e) {
system.error(e)
} finally {
setLoadingModal(false)
}
}).catch(e => {
system.error(e)
})
}
# 引入依赖
除了 antd 组件以及脚手架内置的业务组件,有时我们还需要引入其他外部组件,这里以引入 echarts (opens new window) 为例进行介绍。
在终端输入下面的命令完成安装:
$ yarn add echarts
# 多级目录
默认采用约定式路由,需要多级目录创建对应的文件夹即可。
# 使用图标
antd官方提供了一下简单icon给予我们使用。
但是当复杂的时候我们就需要集成到iconfont (opens new window),进入官网,登录账号 ---> 选择喜欢的icon ---> 上传到我们的iconfont中,获取js保存到本地@/public/js/icon.js即可调用,详细请看官方集成icon (opens new window)
# 使用字典
字典管理是用来维护数据类型的数据,如下拉框、单选按钮、复选框、树选择的数据,方便系统管理员维护。主要功能包括:字典分类管理、字典数据管理
1、页面使用数据字典
import DictionariesSelect from '@/components/TwelveT/Dictionaries/DictionariesSelect/Index'
<DictionariesSelect type='sys_oauth_client_details' />
# 异常处理
@/utils/request.js
是基于 axios
的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。它封装了全局 request拦截器
、response拦截器
、统一的错误处理
、统一做了超时处理
、baseURL设置等
。 如果有自定义错误码可以在codeMessage
中设置对应key
value
值。
/**
* request 网络请求工具
* 更详细的 api 文档: https://github.com/umijs/umi-request
*/
import { extend } from 'umi-request'
import { message, notification } from 'antd'
import { getDvaApp } from 'umi'
import { system } from '@/utils/twelvet'
import TWT from '@/setting'
import { isArray } from 'lodash'
import { logout } from '@/utils/twelvet'
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误/失效)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
}
/**
* 异常处理程序
*/
const errorHandler = async (error: { response: Response }): Promise<Response> => {
const { response } = error
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText
const { status, url } = response
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
})
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
logout()
}
return response
}
/**
* 配置request请求时的默认参数
*/
const request = extend({
// 默认错误处理
errorHandler,
// 超时时间(毫秒)
timeout: 15000,
// 默认请求是否带上cookie(配置后无法跨域)
// credentials: 'include',
})
// 请求前的处理
request.use(
async (ctx, next) => {
try {
const { req } = ctx
const { url, options } = req
if (url.indexOf('http') && (url.indexOf('/api') !== 0)) {
// 给url添加前缀
ctx.req.url = `${TWT.urlPrefix}${url}`
}
// 统一传递的参数名称【get请求时参数传递需要放到params下】
const _method: string = options.method?.toLocaleUpperCase()
if (_method == 'GET' && options.data) {
options.params = {
...options.data
}
}
const local = localStorage.getItem(TWT.accessToken)
const { access_token, expires_in } = local ? JSON.parse(local) : { access_token: '', expires_in: 0 }
// 附加参数
ctx.req.options = {
...options,
requestPath: url,
headers: {
...options.headers,
// 加入认证信息
'Authorization': `Bearer ${access_token}`
}
}
await next()
} catch (e) {
system.error(e)
}
}
)
// Filter【请求后的处理】
request.interceptors.response.use(async (httpResponse, httpRequest) => {
if(httpResponse.status === 504){
notification.error({
description: '服务异常,无法连接',
message: codeMessage[504],
});
throw new Error(codeMessage[504])
}
// blob类型直接返回
if (httpRequest.responseType === 'blob' && httpResponse.status == 200) {
return httpResponse;
}
const data = await httpResponse.clone().json();
// 默认返回
let responseRes = httpResponse
// 处理401状态
if (data.code === 401) {
const { params, method, requestPath } = httpRequest
// 执行刷新token
const res = await getDvaApp()._store.dispatch({
type: 'user/refreshToken',
payload: {
requestPath: requestPath,
method: method,
responseType: httpRequest.responseType,
data: params
}
})
// 存在返回再设置
if (res) {
responseRes = res
}
}
if (data && data.code === 403) {
notification.error({
message: data.msg,
});
// 跳转到登陆页
// return router.replace('/user/login');
} else if (data && data.status === -998) {
// 无操作权限
notification.error({
message: data.msg
});
}
return responseRes;
})
/**
* 通用下载方法
* @param url 地址
* @param params 参数
* @param filename 文件名称(空即为输出默认)
*/
export function download(url: string, params?: { [key: string]: any }, filename?: string) {
return request(`${url}`, {
method: 'POST',
data: {
...params
},
params: {
refresh: new Date().getTime()
},
responseType: 'blob',
parseResponse: false
})
.then((response) => {
// 空的将采用默认
if (!filename) {
const contentDisposition = response.headers.get('content-disposition')
if (!contentDisposition) {
return response.blob()
}
const name = contentDisposition.split("filename=")
if (isArray(name)) {
// 获取并还原编码
filename = decodeURIComponent(name[1])
} else {
filename = 'unknown'
}
}
return response.blob()
})
.then((blob) => {
if ('download' in document.createElement('a')) {
// 非IE下载
const elink = document.createElement('a')
elink.download = filename || 'unknown'
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
} else {
// IE10+下载
navigator.msSaveBlob(blob, filename)
}
}).catch((r) => {
system.error(r)
})
}
/**
* 通用文件上传
* @param url 地址
* @param formData 数据对象 FormData
* @param params 参数
*/
export function upload(url: string, formData: FormData) {
return request(`${url}`, {
method: 'POST',
requestType: 'form',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': '*/*',
},
data: formData,
})
}
export default request
# 应用路径
有些特殊情况需要部署到子路径下,例如:https://www.twelvet.cn/admin
,可以按照下面流程修改。
修改@/config/config.ts
中的manifest
属性
manifest: {
basePath: '/',
},
2、修改nginx
配置
location /admin {
alias /home/twelvet/projects/twelvet-ui;
try_files $uri $uri/ /index.html =404;
index index.html index.htm;
}
打开浏览器,输入:https://www.twelvet.cn/admin
能正常访问和刷新表示成功。