vue2.7+element-ui
下载文件
/** api */
// 批量导入模板下载
export function userTemplateAPI () {
return request({
responseType: 'blob',
method: 'post',
url: '/api/sys/user/template',
})
}
/** 业务页面 */
import { downloadFile } from '@/utils'
//导入模板下载
async function downloadTemplate () {
//下载文件
try {
const res: any = await userTemplateAPI()
downloadFile(res)
} catch (error) {
console.log('error: ', error)
}
}
/** 下载方法 */
export function downloadFile (
res: NormalizedResponse<Blob>,
fileName?: string
): void {
let name = ''
const { headers, data } = res as any
try {
name = decodeURI(
fileName || (headers['content-disposition'] || '').split('filename=')[1]
)
const blob = new Blob([data])
saveAs(blob, name)
} catch (e) {
console.error(e)
}
}
注: 发送请求时 responseType: 'blob', 是关键
async function handleExport () {
if(errorSelected.value.length === 0) return
const res = await erroExportAPI(errorSelected.value)
const blob = new Blob([res.data], {
type: 'application/vnd.ms-excel; charset=uft-8',
})
const a = document.createElement('a')
const url = window.URL.createObjectURL(blob)
a.href = url
a.download = '上传失败原因.xlsx'
a.click()
window.URL.revokeObjectURL(url)
}
blob文件转JSON文件
export function batchUserImportAPI (filePath: string) {
return request({
method: 'post',
responseType: 'blob', //当接口请求存在blob 时
params: {fileId: filePath},
url: _c('/api/sys/user/import', false),
})
}
function blobToJson (blob: Blob) {
return new Promise((resolve, reject) =>{
const reader: any = new FileReader()
reader.onload = () =>{
resolve(JSON.parse(reader.result))
}
reader.onerror = () =>{
reject(new Error('Unable to read blob as JSON'))
}
reader.readAsText(blob)
})
}
function upAddBatch () {
//批量新增
submitLoading.value = true
batchUserImportAPI(fileIds.value[0]).then((res) => {
if(res.data.type == 'application/json'){ //json文件时
const blob = new Blob([res.data], { type: 'application/json' })
blobToJson(blob).then((data: any) =>{
if(data?.code == '00000') {
closed()
ElMessageBox.alert(data.data, {
type: 'success',
confirmButtonText: '知道了'
}).finally(() => {
// emits('upload-success')
})
return
}else {
ElMessage.error(data.message)
}
return
})
}else{ //下载接口返回的文件
ElMessage.error('导入失败')
downloadFile(res as any)
return
}
}).finally(() => {
submitLoading.value = false
})
}
el-dropdown-menu 首次点击无效
把@click 改为 @click.native
@click 写在 el-dropdown-item 上
<el-dropdown-menu
:hide-timeout="0"
class="dropdown-menu"
>
<el-dropdown-item @click.native="changePassword">
<span
style="display: block"
>
修改密码
</span>
</el-dropdown-item>
<el-dropdown-item
divided
@click.native="handleLogout"
>
<span
style="display: block"
>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
el-form 校验提取
const formRules = {
oldPass: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
newPass: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ validator: checkNewPass, trigger: ['blur'] }
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{ validator: validataConfirmPassword, trigger: ['blur'] }
],
name:[
{ required: true, message: '请输入用户姓名', trigger: 'blur' },
{
required: true,
validator: (rule: unknown, value: string, cb: Function) => {
const regex = /[^\u4e00-\u9fa5\\^a-zA-Z]/g
if (!value) {
cb(new Error('请输入用户账号'))
} else if(regex.test(value)) {
cb(new Error('只能输入汉字和英文'))
}else {
cb()
}
},
triggers: ['blur', 'change']
}
],
}
function validataConfirmPassword (rule: any, value: string,cb: any) {
if(!value){
cb(new Error('请再次输入密码'))
} else if(value !== formData.value.newPass) {
cb(new Error('两次密码不一致'))
} else {
cb()
}
}
function checkNewPass (rule: any, value: string,cb: any) {
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#¥%&*(){}])[A-Za-z\d!@#¥%&*(){}]{6,}$/
if(!value){
cb(new Error('请输入新密码'))
}else if(value.length < 6 || value.length > 20){
cb(new Error('密码长度为6-20位'))
}
else if(!regex.test(value)) {
cb(new Error('密码需包含大小写字母、数字、特殊符号组成 特殊字符包含!@#¥%&*(){}'))
}
}
时间选择器快捷键
<el-date-picker
v-model="filterParam.time"
type="datetimerange"
:picker-options="pickerOptions"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
align="right"
>
</el-date-picker>
const pickerOptions = {
shortcuts: [
{
text: "昨天",
onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
const end = new Date()
const start = new Date()
start.setTime(new Date(new Date().toLocaleDateString()).getTime() - 24 * 60 * 60 * 1000)
end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
picker.$emit("pick", [start, end])
},
},
{
text: "最近一周",
onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
const end = new Date()
const start = new Date()
end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
start.setTime(end.getTime() - 3600 * 1000 * 24 * 7 + 1)
picker.$emit("pick", [start, end])
},
},
{
text: "最近一个月",
onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
const end = new Date()
const start = new Date()
end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
start.setTime(end.getTime() - 3600 * 1000 * 24 * 31 + 1)
picker.$emit("pick", [start, end])
},
},
{
text: "最近三个月",
onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
const end = new Date()
const start = new Date()
end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
start.setTime(end.getTime() - 3600 * 1000 * 24 * 31 * 3 + 1)
picker.$emit("pick", [start, end])
},
},
],
}
时间选择器未来日期不可选
<el-date-picker
v-model="filterParam.time[1]"
type="date"
placeholder="结束时间"
:picker-options="setDisabled"
@change="handleFilterChange"
>
</el-date-picker>
const setDisabled = {
disabledDate(time: any) {
return time.getTime() > Date.now(); // 可选历史天、可选当前天、不可选未来天
// return time.getTime() > Date.now() - 8.64e7; // 可选历史天、不可选当前天、不可选未来天
// return time.getTime() < Date.now() - 8.64e7; // 不可选历史天、可选当前天、可选未来天
// return time.getTime() < Date.now(); // 不可选历史天、不可选当前天、可选未来天
}
}
时间选择器多重条件禁用
时间选择器未来日期不可选&&结束日期不能早于开始日期&&最长选择范围一年
<el-date-picker
v-model="filterParam.time[0]"
type="date"
placeholder="开始时间"
:picker-options="setDisabledStart"
@change="search"
></el-date-picker>
<span>至</span>
<el-date-picker
v-model="filterParam.time[1]"
type="date"
placeholder="结束时间"
:picker-options="setDisabledEnd"
@change="search"
></el-date-picker>
const filterParam = ref({
time: [getWeekTime(),dayjs().format()],
})
const setDisabledStart = {
disabledDate(time: any) {
//不能选未来,及结束日期一年期那的日期
return time.getTime() > Date.now() || time.getTime() < +dayjs(filterParam.value.time[1]) - (365 * 24 * 3600 * 1000)
}
}
const setDisabledEnd = {
disabledDate(time: any) {
//不能选未来,开始日期前的日期
return time.getTime() > Date.now() || time.getTime() < +dayjs(filterParam.value.time[0])
}
}
自定义下拉框字体颜色
<el-select
v-model="filterFields.stat"
placeholder="Select"
@change="search"
>
<el-option
v-for="item in statAll"
:key="item.value"
:label="item.label"
:value="item.value"
v-html="'<span style=color:' + item.color + '>'+item.label+'</span>'"
/>
</el-select>
const filterFields = ref({
stat: statAll[0].value //状态
})
const statAll = [
{
label:'全部',
value:'-1',
color: '#000'
},
{
label:'运行中',
value:'0',
color: '#70e17b'
},
{
label:'异常',
value:'1',
color: '#e62000'
},
{
label:'空闲',
value:'2',
color: '#666666'
},
]
base64图片的显示(验证码)
<el-image :src="'data:image/png;base64,'+captchaSrc" @click="login" />
async function getCaptcha (){
await getVerifyApi()
.then((res) => {
captchaSrc.value = res.data //base64数据
})
}
onMounted(() => {
getCaptcha()
})
Blob 图片显示(验证码)
重点
{ responseType: 'blob' } 是重点
//获取验证码
export function getVerifyApi () {
return request.get('/verify', { responseType: 'blob' })
}
<el-image
:src="captchaSrc"
@click="getCaptcha"
class="login-form__captcha"
/>
async function getCaptcha (){
await getVerifyApi().then((res) => {
const blob = new Blob([res.data], {type: 'image/jpeg'})
captchaSrc.value = window.URL.createObjectURL(blob)
})
}
onMounted(() => {
getCaptcha()
})
Blob 图片显示(验证码)
<el-image
:src="captchaSrc"
@click.native="toggleCaptcha"
class="login-form__captcha"
>
<div
slot="placeholder"
class="image-slot"
>
加载中<span class="dot">...</span>
</div>
</el-image>
import throttle from 'lodash/throttle'
const captchaSrc = ref('')
const toggleCaptcha = throttle(() => {
getCaptchaSrc().then(({ data, axiosRes: { headers } }) => {
const blob = new Blob([data], {type: headers.contentType ?? ''})
captchaSrc.value = window.URL.createObjectURL(blob)
})
}, 2000, { trailing: false })
onMounted(() => {
toggleCaptcha()
if (loginForm.value.username === '') {
usernameRef.value?.focus()
} else if (loginForm.value.password === '') {
passwordRef.value?.focus()
}
})
axios 封装
@/utils/request
待完善
import axios from 'axios'
const service = axios.create({
baseURL,
timeout: 0
})
export default service
@/utils/index
/**
* 在开发环境下,拼接 mock api 的请求地址
* 若程序运行在生产环境中,将原样返回传入的请求地址,不再进行拼接
*
* @param path 相对路径, 从 Yapi 平台直接复制即可
* @param mock 是否需要给路径增加 mock 所需的前缀
*/
export function _c(path: string, mock = false) {
if (IS_PRODUCTION) {
return path
} else {
return mock
? '/mock' + path
: path
}
}
@/api/index
import request from '@/utils/request'
import { _c } from '@/utils'
export function getNodeApi(params: any) {
return request.get(_c('/node/all', isMock), { params: params })
}
export function updataNodeApi(data: addNodeApiType) {
return request.post(_c('/node', isMock), data)
}
export function addNodeApi(data: addNodeApiType) {
return request.put(_c('/node', isMock), data)
}
export function delNodeApi(id: string) {
return request.delete(_c(`/node/${id}`, isMock))
}