添加新的需求

This commit is contained in:
xuqiuyun
2025-10-20 17:32:09 +08:00
parent 9979e00b47
commit 361d5ab1ae
247 changed files with 34249 additions and 1 deletions

View File

@@ -0,0 +1,300 @@
/**
* 通用js方法封装处理
* Copyright (c) 2019 ruoyi
*/
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null;
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
let date;
if (typeof time === 'object') {
date = time;
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
// eslint-disable-next-line no-param-reassign
time = parseInt(time, 10);
} else if (typeof time === 'string') {
// eslint-disable-next-line no-param-reassign
time = time
// eslint-disable-next-line prefer-regex-literals
.replace(new RegExp(/-/gm), '/')
.replace('T', ' ')
// eslint-disable-next-line prefer-regex-literals
.replace(new RegExp(/\.[\d]{3}/gm), '');
}
if (typeof time === 'number' && time.toString().length === 10) {
// eslint-disable-next-line no-param-reassign
time *= 1000;
}
date = new Date(time);
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
};
const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value];
}
if (result.length > 0 && value < 10) {
value = `0${value}`;
}
return value || 0;
});
return timeStr;
}
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields();
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
const search = params;
search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
// eslint-disable-next-line no-param-reassign
dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof propName === 'undefined') {
// eslint-disable-next-line prefer-destructuring
search.params.beginTime = dateRange[0];
// eslint-disable-next-line prefer-destructuring
search.params.endTime = dateRange[1];
} else {
// eslint-disable-next-line prefer-destructuring
search.params[`begin${propName}`] = dateRange[0];
// eslint-disable-next-line prefer-destructuring
search.params[`end${propName}`] = dateRange[1];
}
return search;
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return '';
}
const actions = [];
Object.keys(datas).some((key) => {
if (datas[key].value === `${value}`) {
actions.push(datas[key].label);
return true;
}
});
if (actions.length === 0) {
actions.push(value);
}
return actions.join('');
}
// 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length === 0) {
return '';
}
if (Array.isArray(value)) {
// eslint-disable-next-line no-param-reassign
value = value.join(',');
}
const actions = [];
const currentSeparator = undefined === separator ? ',' : separator;
const temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => {
let match = false;
Object.keys(datas).some((key) => {
if (datas[key].value == `${temp[val]}`) {
actions.push(datas[key].label + currentSeparator);
match = true;
}
});
if (!match) {
actions.push(temp[val] + currentSeparator);
}
});
return actions.join('').substring(0, actions.join('').length - 1);
}
// 字符串格式化(%s )
export function sprintf(str) {
const args = arguments;
let flag = true;
let i = 1;
str = str.replace(/%s/g, function () {
const arg = args[i++];
if (typeof arg === 'undefined') {
flag = false;
return '';
}
return arg;
});
return flag ? str : '';
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == 'undefined' || str == 'null') {
return '';
}
return str;
}
// 数据合并
export function mergeRecursive(source, target) {
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const p in target) {
try {
if (target[p].constructor === Object) {
// eslint-disable-next-line no-param-reassign
source[p] = mergeRecursive(source[p], target[p]);
} else {
// eslint-disable-next-line no-param-reassign
source[p] = target[p];
}
} catch (e) {
// eslint-disable-next-line no-param-reassign
source[p] = target[p];
}
}
return source;
}
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
const config = {
id: id || 'id',
parentId: parentId || 'pid',
childrenList: children || 'children',
};
const childrenListMap = {};
const nodeIds = {};
const tree = [];
// eslint-disable-next-line no-restricted-syntax
for (const d of data) {
// eslint-disable-next-line no-shadow
const parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
// eslint-disable-next-line no-restricted-syntax
for (const d of data) {
// eslint-disable-next-line no-shadow
const parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
}
}
// eslint-disable-next-line no-restricted-syntax
for (const t of tree) {
// eslint-disable-next-line no-use-before-define
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
// eslint-disable-next-line no-param-reassign
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
// eslint-disable-next-line no-restricted-syntax
for (const c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = '';
// eslint-disable-next-line no-restricted-syntax
for (const propName of Object.keys(params)) {
const value = params[propName];
const part = `${encodeURIComponent(propName)}=`;
if (value !== null && value !== '' && typeof value !== 'undefined') {
if (typeof value === 'object') {
// eslint-disable-next-line no-restricted-syntax
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
// eslint-disable-next-line no-shadow
const params = `${propName}[${key}]`;
const subPart = `${encodeURIComponent(params)}=`;
result += `${subPart + encodeURIComponent(value[key])}&`;
}
}
} else {
result += `${part + encodeURIComponent(value)}&`;
}
}
}
return result;
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == 'undefined') {
return p;
}
const res = p.replace('//', '/');
if (res[res.length - 1] === '/') {
return res.slice(0, res.length - 1);
}
return res;
}
// 验证是否为blob格式
export function blobValidate(data) {
return data.type !== 'application/json';
}
// 扫描二维码获取设备id过滤
export function getDeviceId(url) {
let size = '';
// 2406501594
if (!isNaN(Number(url))) {
size = url;
} else if (url.indexOf('/jbq/') != -1) {
// https://iot.aiotagro.com/jbq/2406501594
size = url.split('/jbq/')[url.split('/jbq/').length - 1];
} else if (url.indexOf('device_sn') != -1) {
// https://farm.aiotagro.com/source/index.htmldevicesn=2408400279&嚓te&t=1730775527
const parsedUrl = new URL(url);
size = parsedUrl.searchParams.get('device_sn');
// https://farm.aiotagro.com/source/index.html?device_sn=2408400301
// size = url.split('devicesn=')[url.split('devicesn=').length-1]
} else {
return '';
}
return size;
}

View File

@@ -0,0 +1,15 @@
import Cookies from 'js-cookie';
const TokenKey = 'Admin-Token';
export function getToken() {
return Cookies.get(TokenKey);
}
export function setToken(token) {
return Cookies.set(TokenKey, token);
}
export function removeToken() {
return Cookies.remove(TokenKey);
}

View File

@@ -0,0 +1,141 @@
import Axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
// import { useUserStore } from "~/store/user";
// const userStore=useUserStore();
export let isRelogin = { show: false };
// @ts-ignore
Axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'; //设置前后端请求文档的类型
// 开发环境使用 /api 前缀,让 vite proxy 代理生效
// 生产环境使用环境变量配置的域名
const baseURL: any = import.meta.env.VITE_API_DOMAIN || '/api'; //设置统一接口地址前缀
const axios = Axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL,
timeout: 120000,
});
// 前置拦截器(发起请求之前的拦截)
axios.interceptors.request.use(
(config) => {
let userStore = localStorage.getItem('userStore');
let token = '';
if (userStore) {
const us = JSON.parse(userStore);
token = us.token;
}
if (token) {
// @ts-ignore
config.headers.Authorization = token;
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
// 后置拦截器(获取到响应时的拦截)
axios.interceptors.response.use(
(response) => {
//后端没报错
/**
* 根据你的项目实际情况来对 response 和 error 做处理
* 这里对 response 和 error 不做任何处理,直接返回
*/
const responseData = response.data;
// 单独判断导出问题-start
if(toString.call(responseData) === '[object Blob]' && (responseData.type !== 'application/json')) {
download(response);
return ElMessage({
type: 'success',
message: '导出成功!',
});
}else if(toString.call(responseData) === '[object Blob]' && (responseData.type == 'application/json')){
setTimeout(() => {
window.location.href = '/login';
}, 1000);
return ElMessage.error(`登录过期,请重新登录!`);
}
// 单独判断导出问题-end
if (responseData && (responseData.code == 200 || responseData.status == 0)) {
return Promise.resolve(response.data); //成功==》可以执行then方法
}else if(responseData.code === 401){
setTimeout(() => {
window.location.href = '/login';
}, 1000);
return ElMessage.error(`登录过期,请重新登录!`);
}
// console.log(responseData);
// if (responseData && (responseData.code === 650)) {
// ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning',
// })
// .then(() => {
// window.location.href = '/login';
// })
// .catch(() => {});
// // eslint-disable-next-line prefer-promise-reject-errors
// return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
// }
const { msg } = responseData;
ElMessage.error(msg?.length > 50 ? `抱歉!当前系统繁忙,请您刷新页面或稍后再试.` : msg);
// ElMessage.error(`抱歉!当前系统繁忙,请您刷新页面或稍后再试。`);
return Promise.reject(msg);
},
(error) => {
//后端自己的接口报错
if(error.code == "ECONNABORTED") {
ElMessage.error(`抱歉!网络请求超时,请稍后再试!`);
return Promise.reject(error);
}
if (error.response.status === 509) {
const html = error.response.data;
}
if (error.response && error.response.data) {
if (error.response.status !== 509 && error.response.status !== 401) {
ElMessage.error(`抱歉!当前系统繁忙,请您刷新页面或稍后再试。`);
}
} else {
// ElMessage.error(`${error}`);
ElMessage.error(`抱歉!当前系统繁忙,请您刷新页面或稍后再试。`);
}
return Promise.reject(error);
},
);
// 下载文件
function download(res: any) {
const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' }); // type是文件类详情可以参阅blob文件类型
// 创建新的URL并指向File对象或者Blob对象的地址
const blobURL = window.URL.createObjectURL(blob);
// 创建a标签用于跳转至下载链接
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = blobURL;
if(res.headers['content-disposition']){
tempLink.setAttribute('download', decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1]));
}
// 兼容某些浏览器不支持HTML5的download属性
if (typeof tempLink.download === 'undefined') {
tempLink.setAttribute('target', '_blank');
}
// 挂载a标签
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
// 释放blob URL地址
window.URL.revokeObjectURL(blobURL);
}
export default axios;

View File

@@ -0,0 +1,6 @@
export default {
401: '认证失败,无法访问系统资源',
403: '当前操作没有权限',
404: '访问资源不存在',
default: '系统未知错误,请反馈给管理员',
};

View File

@@ -0,0 +1,125 @@
/**
* 图片处理工具函数
*/
/**
* 处理逗号分隔的图片URL
* @param {string} imageUrl - 逗号分隔的图片URL字符串
* @returns {Array} 图片URL数组
*/
export const getImageList = (imageUrl) => {
if (!imageUrl || imageUrl.trim() === '') {
return [];
}
// 按逗号分割并过滤空字符串
return imageUrl.split(',').map(url => url.trim()).filter(url => url !== '');
};
/**
* 获取代理图片URL如果后端支持
* @param {string} originalUrl - 原始图片URL
* @returns {string} 代理后的URL或原始URL
*/
export const getProxyImageUrl = (originalUrl) => {
if (!originalUrl || !originalUrl.startsWith('http')) {
return originalUrl;
}
// 使用后端代理访问图片避免CORS问题
// 注意这里需要完整的URL路径
return `/api/member/proxy/image?url=${encodeURIComponent(originalUrl)}`;
};
/**
* 测试图片URL的可访问性
* @param {string} imageUrl - 图片URL
* @returns {Promise<boolean>} 是否可访问
*/
export const testImageUrl = async (imageUrl) => {
try {
console.log('测试图片URL:', imageUrl);
const response = await fetch(imageUrl, {
method: 'HEAD',
mode: 'cors'
});
console.log('图片URL测试结果:', {
url: imageUrl,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
});
return response.ok;
} catch (error) {
console.error('图片URL测试失败:', imageUrl, error);
return false;
}
};
/**
* 处理图片加载错误
* @param {string} imageUrl - 失败的图片URL
* @param {number} index - 图片索引
*/
export const handleImageError = (imageUrl, index) => {
// 如果是腾讯云COS图片这是预期的CORS错误不需要详细日志
if (imageUrl.includes('cos.ap-guangzhou.myqcloud.com')) {
console.log(`腾讯云COS图片加载失败 (索引: ${index}):`, imageUrl);
console.log('这是预期的CORS错误图片将显示占位符');
return;
}
// 其他图片的错误才显示详细日志
console.error(`图片加载失败 (索引: ${index}):`, imageUrl);
console.error('可能的原因:');
console.error('1. URL格式不正确');
console.error('2. 服务器无法访问');
console.error('3. CORS跨域问题');
console.error('4. 网络连接问题');
// 尝试手动验证URL
try {
const url = new URL(imageUrl);
console.error('URL解析成功:', url.href);
console.error('协议:', url.protocol);
console.error('主机:', url.hostname);
console.error('路径:', url.pathname);
} catch (e) {
console.error('URL格式无效:', e.message);
}
};
/**
* 获取图片列表(带代理支持)
* @param {string} imageUrl - 逗号分隔的图片URL字符串
* @param {boolean} useProxy - 是否使用代理
* @returns {Array} 处理后的图片URL数组
*/
export const getImageListWithProxy = (imageUrl, useProxy = false) => {
const urls = getImageList(imageUrl);
if (useProxy) {
return urls.map(url => getProxyImageUrl(url));
}
return urls;
};
/**
* 智能图片URL处理 - 根据域名决定是否使用代理
* @param {string} imageUrl - 逗号分隔的图片URL字符串
* @returns {Array} 处理后的图片URL数组
*/
export const getImageListSmart = (imageUrl) => {
const urls = getImageList(imageUrl);
return urls.map(url => {
// 如果是腾讯云COS的图片暂时直接返回原始URL
// 浏览器会显示占位符,但不会报错
if (url.includes('cos.ap-guangzhou.myqcloud.com')) {
console.log('检测到腾讯云COS图片暂时使用原始URL显示占位符:', url);
return url; // 暂时直接返回,让浏览器显示占位符
}
// 其他图片直接返回原始URL
console.log('其他图片直接使用原始URL:', url);
return url;
});
};

View File

@@ -0,0 +1,30 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min';
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey =
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==';
const privateKey =
'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
'UP8iWi1Qw0Y=';
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey); // 设置公钥
return encryptor.encrypt(txt); // 对数据进行加密
}
// 解密
export function decrypt(txt) {
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(privateKey); // 设置私钥
return encryptor.decrypt(txt); // 对数据进行解密
}

View File

@@ -0,0 +1,13 @@
export function BMPGL(ak) {
return new Promise(function (resolve, reject) {
window.init = function () {
// eslint-disable-next-line
resolve(BMapGL);
};
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `http://api.map.baidu.com/api?v=3.0&type=webgl&ak=${ak}&callback=init`;
script.onerror = reject;
document.head.appendChild(script);
});
}

View File

@@ -0,0 +1,49 @@
import useUserStore from '~/store/modules/user';
/**
* 字符权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export function checkPermi(value) {
if (value && value instanceof Array && value.length > 0) {
const { permissions } = useUserStore();
const permissionDatas = value;
const all_permission = '*:*:*';
const hasPermission = permissions.some((permission) => {
return all_permission === permission || permissionDatas.includes(permission);
});
if (!hasPermission) {
return false;
}
return true;
}
console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
return false;
}
/**
* 角色权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export function checkRole(value) {
if (value && value instanceof Array && value.length > 0) {
const { roles } = useUserStore();
const permissionRoles = value;
const super_admin = 'admin';
const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role);
});
if (!hasRole) {
return false;
}
return true;
}
console.error(`need roles! Like checkRole="['admin','editor']"`);
return false;
}

View File

@@ -0,0 +1,187 @@
import axios from 'axios';
import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus';
import { saveAs } from 'file-saver';
import { getToken } from '~/utils/auth';
import errorCode from '~/utils/errorCode';
import { tansParams, blobValidate } from '~/utils/ruoyi';
import cache from '~/plugins/cache';
let downloadLoadingInstance;
// 是否显示重新登录
export const isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API || '/api',
// 超时
timeout: 10000,
});
// request拦截器
service.interceptors.request.use(
(config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
// 优先从localStorage获取token与axios.ts保持一致
let token = null;
const userStoreStr = localStorage.getItem('userStore');
if (userStoreStr) {
try {
const userStore = JSON.parse(userStoreStr);
token = userStore.token;
} catch (e) {
console.error('解析userStore失败:', e);
}
}
// 如果localStorage没有则从Cookie获取
if (!token) {
token = getToken();
}
if (token && !isToken) {
// 检查token是否已经包含Bearer前缀
if (token.startsWith('Bearer ')) {
config.headers.Authorization = token;
} else {
config.headers.Authorization = `Bearer ${token}`;
}
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = `${config.url}?${tansParams(config.params)}`;
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime(),
};
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。');
return config;
}
const sessionObj = cache.session.getJSON('sessionObj');
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj);
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ${message}`);
return Promise.reject(new Error(message));
}
cache.session.setJSON('sessionObj', requestObj);
}
}
return config;
},
(error) => {
Promise.reject(error);
},
);
// 响应拦截器
service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode.default;
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data;
}
if (code === 401 || code === 650) {
if (!isRelogin.show) {
isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
isRelogin.show = false;
location.href = '/login';
})
.catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
}
if (code === 500) {
ElMessage({ message: msg, type: 'error' });
return Promise.reject(new Error(msg));
}
if (code === 601) {
ElMessage({ message: msg, type: 'warning' });
return Promise.reject(new Error(msg));
}
if (code !== 200) {
ElNotification.error({ title: msg });
return Promise.reject('error');
}
return Promise.resolve(res.data);
},
(error) => {
let { message } = error;
if (message == 'Network Error') {
message = '后端接口连接异常';
} else if (message.includes('timeout')) {
message = '系统接口请求超时';
} else if (message.includes('Request failed with status code')) {
message = `系统接口${message.substr(message.length - 3)}异常`;
}
ElMessage({ message, type: 'error', duration: 5 * 1000 });
return Promise.reject(error);
},
);
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
return service
.post(url, params, {
transformRequest: [
(params) => {
return tansParams(params);
},
],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config,
})
.then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data]);
saveAs(blob, filename);
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode.default;
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
})
.catch((r) => {
console.error(r);
ElMessage.error('下载文件出现错误,请联系管理员!');
downloadLoadingInstance.close();
});
}
export default service;

View File

@@ -0,0 +1,43 @@
/**
* 通用js方法封装处理
*/
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = '';
for (const propName of Object.keys(params)) {
const value = params[propName];
const part = `${encodeURIComponent(propName)}=`;
if (value !== null && value !== '' && typeof value !== 'undefined') {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
const params = `${propName}[${key}]`;
const subPart = `${encodeURIComponent(params)}=`;
result += `${subPart + encodeURIComponent(value[key])}&`;
}
}
} else {
result += `${part + encodeURIComponent(value)}&`;
}
}
}
return result;
}
/**
* 验证是否为blob格式
*/
export async function blobValidate(data) {
try {
const text = await data.text();
JSON.parse(text);
return false;
} catch (error) {
return true;
}
}

View File

@@ -0,0 +1,67 @@
Math.easeInOutQuad = function (t, b, c, d) {
// eslint-disable-next-line no-param-reassign
t /= d / 2;
if (t < 1) {
return (c / 2) * t * t + b;
}
// eslint-disable-next-line no-param-reassign,no-plusplus
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
};
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
const requestAnimFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
})();
/**
* Because it's so fucking difficult to detect the scrolling element, just move them all
* @param {number} amount
*/
function move(amount) {
document.documentElement.scrollTop = amount;
document.body.parentNode.scrollTop = amount;
document.body.scrollTop = amount;
}
function position() {
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop;
}
/**
* @param {number} to
* @param {number} duration
* @param {Function} callback
*/
// eslint-disable-next-line import/prefer-default-export
export function scrollTo(to, duration, callback) {
const start = position();
const change = to - start;
const increment = 20;
let currentTime = 0;
// eslint-disable-next-line no-param-reassign
duration = typeof duration === 'undefined' ? 500 : duration;
const animateScroll = function () {
// increment the time
currentTime += increment;
// find the value with the quadratic in-out easing function
const val = Math.easeInOutQuad(currentTime, start, change, duration);
// move the document.body
move(val);
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll);
} else if (callback && typeof callback === 'function') {
// the animation is done so lets callback
callback();
}
};
animateScroll();
}

View File

@@ -0,0 +1,31 @@
// post请求
export const post = (config) => {
return Service({
...config,
method: 'post',
data: config.data,
});
};
// get请求
export const get = (config) => {
return Service({
...config,
method: 'get',
params: config.data,
});
};
// put请求
export const put = (config) => {
return Service({
...config,
method: 'put',
data: config.data,
});
};
// delete请求
export const del = (config) => {
return Service({
...config,
method: 'delete',
});
};

View File

@@ -0,0 +1,165 @@
import { getMenuList, assignRoleMenus, getUserList } from '@/api/permission.js';
import { ElMessage } from 'element-plus';
/**
* 为超级管理员账户分配所有菜单权限
* @param {string} mobile 手机号,默认为 '15900000000'
* @returns {Promise<boolean>} 是否成功
*/
export async function assignAllPermissionsToSuperAdmin(mobile = '15900000000') {
try {
console.log(`=== 开始为超级管理员 ${mobile} 分配所有菜单权限 ===`);
// 1. 获取用户列表,找到目标用户
const userListRes = await getUserList();
if (userListRes.code !== 200) {
throw new Error('获取用户列表失败');
}
const targetUser = userListRes.data.find(user => user.mobile === mobile);
if (!targetUser) {
throw new Error(`未找到手机号为 ${mobile} 的用户`);
}
console.log('=== 找到目标用户 ===', targetUser);
// 2. 获取所有菜单列表
const menuListRes = await getMenuList();
if (menuListRes.code !== 200) {
throw new Error('获取菜单列表失败');
}
const allMenus = menuListRes.data || [];
const allMenuIds = allMenus.map(menu => menu.id);
console.log('=== 获取到所有菜单 ===', {
totalMenus: allMenus.length,
menuIds: allMenuIds
});
// 3. 为超级管理员角色分配所有菜单权限
const assignRes = await assignRoleMenus({
roleId: targetUser.roleId,
menuIds: allMenuIds
});
if (assignRes.code === 200) {
console.log('=== 权限分配成功 ===');
ElMessage.success(`成功为超级管理员 ${mobile} 分配了 ${allMenuIds.length} 个菜单权限`);
return true;
} else {
throw new Error(assignRes.msg || '权限分配失败');
}
} catch (error) {
console.error('=== 权限分配失败 ===', error);
ElMessage.error(`权限分配失败: ${error.message}`);
return false;
}
}
/**
* 检查超级管理员权限状态
* @param {string} mobile 手机号,默认为 '15900000000'
* @returns {Promise<Object>} 权限状态信息
*/
export async function checkSuperAdminPermissions(mobile = '15900000000') {
try {
console.log(`=== 检查超级管理员 ${mobile} 的权限状态 ===`);
// 1. 获取用户列表,找到目标用户
const userListRes = await getUserList();
if (userListRes.code !== 200) {
throw new Error('获取用户列表失败');
}
const targetUser = userListRes.data.find(user => user.mobile === mobile);
if (!targetUser) {
throw new Error(`未找到手机号为 ${mobile} 的用户`);
}
// 2. 获取该用户当前已分配的菜单权限
const { getRoleMenuIds } = await import('@/api/permission.js');
const roleMenuRes = await getRoleMenuIds(targetUser.roleId);
if (roleMenuRes.code !== 200) {
throw new Error('获取角色菜单权限失败');
}
const assignedMenuIds = roleMenuRes.data || [];
// 3. 获取所有菜单数量
const menuListRes = await getMenuList();
if (menuListRes.code !== 200) {
throw new Error('获取菜单列表失败');
}
const totalMenus = menuListRes.data.length;
const status = {
user: targetUser,
totalMenus: totalMenus,
assignedMenus: assignedMenuIds.length,
isComplete: assignedMenuIds.length === totalMenus,
missingMenus: totalMenus - assignedMenuIds.length
};
console.log('=== 权限状态检查结果 ===', status);
return status;
} catch (error) {
console.error('=== 权限状态检查失败 ===', error);
throw error;
}
}
/**
* 一键为超级管理员分配所有权限包含UI提示
* @param {string} mobile 手机号,默认为 '15900000000'
*/
export async function quickAssignAllPermissions(mobile = '15900000000') {
try {
// 先检查当前状态
const status = await checkSuperAdminPermissions(mobile);
if (status.isComplete) {
ElMessage.info(`超级管理员 ${mobile} 已经拥有所有菜单权限`);
return true;
}
// 确认操作
const confirmed = await new Promise((resolve) => {
ElMessageBox.confirm(
`确定要为超级管理员 ${mobile} 分配所有菜单权限吗?\n\n当前状态:\n- 总菜单数:${status.totalMenus}\n- 已分配:${status.assignedMenus}\n- 缺失:${status.missingMenus}`,
'确认分配权限',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => resolve(true)).catch(() => resolve(false));
});
if (!confirmed) {
ElMessage.info('已取消权限分配');
return false;
}
// 执行分配
const success = await assignAllPermissionsToSuperAdmin(mobile);
if (success) {
// 重新检查状态
const newStatus = await checkSuperAdminPermissions(mobile);
ElMessage.success(`权限分配完成!现在拥有 ${newStatus.assignedMenus}/${newStatus.totalMenus} 个菜单权限`);
}
return success;
} catch (error) {
console.error('=== 快速分配权限失败 ===', error);
ElMessage.error(`操作失败: ${error.message}`);
return false;
}
}

View File

@@ -0,0 +1,90 @@
// 判断给定的字符串是否是有效的用户名
export function validUsername(str) {
const valid_map = ['admin', 'editor'];
return valid_map.indexOf(str.trim()) >= 0;
}
/**
* 判断url是否是http或https
* @param {string} path
* @returns {Boolean}
*/
export function isHttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
}
// 数字转化为大写金额
export function digitUppercase(n) {
const fraction = ['角', '分'];
const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const unit = [
['元', '万', '亿'],
['', '拾', '佰', '仟'],
];
// eslint-disable-next-line no-param-reassign
n = Math.abs(n);
let s = '';
// eslint-disable-next-line no-plusplus
for (let i = 0; i < fraction.length; i++) {
// eslint-disable-next-line no-restricted-properties
s += (digit[Math.floor(n * 10 * 10 ** i) % 10] + fraction[i]).replace(/零./, '');
}
s = s || '整';
// eslint-disable-next-line no-param-reassign
n = Math.floor(n);
// eslint-disable-next-line no-plusplus
for (let i = 0; i < unit[0].length && n > 0; i++) {
let p = '';
// eslint-disable-next-line no-plusplus
for (let j = 0; j < unit[1].length && n > 0; j++) {
p = digit[n % 10] + unit[1][j] + p;
// eslint-disable-next-line no-param-reassign
n = Math.floor(n / 10);
}
s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
}
return s
.replace(/(零.)*零元/, '元')
.replace(/(零.)+/g, '零')
.replace(/^整$/, '零元整');
}
// 将一个数组中的空元素去掉
export function cleanArray(actual) {
const newArray = [];
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i]);
}
}
return newArray;
}
// 数组去重
export function uniqueArr(arr) {
return Array.from(new Set(arr));
}
// 将URL中的查询参数转换为对象
export function param2Obj(url) {
const search = url.split('?')[1];
if (!search) {
return {};
}
return JSON.parse(`{"${decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"')}"}`);
}
// 将对象转为为数组 =====》 待验证
export function toNumericPairs(obj) {
const entries = Object.entries(input);
return entries.map((entry) => [Number(entry[0]), entry[1]]);
}
/**
* 判断path是否为外链
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}

View File

@@ -0,0 +1,79 @@
// 验证手机号
export function checkMobile(str) {
const regMobile = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
return regMobile.test(str);
}
// 验证座机号
export function checkPhone(str) {
const isPhone = /^([0-9]{3,4}-)?[0-9]{7,8}$/;
return isPhone.test(str);
}
// 手机号中间四位变成 *
export function telFormat(tel) {
tel = String(tel);
return `${tel.substr(0, 3)}****${tel.substr(7)}`;
}
// 验证身份证
export function isIdCard(str) {
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return reg.test(str);
}
// 验证名字
export function isChineseName(str) {
const reg = /^[\u4E00-\u9FA5]{2,4}$/;
return reg.test(str);
}
// 验证邮箱
export function checkEmail(str) {
const reg = /^([0-9A-Za-z\-_\.]+)@([0-9a-z]+\.[a-z]{2,3}(\.[a-z]{2})?)$/g;
return reg.test(str);
}
// 验证网址是否有效
export function validURL(url) {
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
return reg.test(url);
}
// 验证url是否是http或https
export function isHttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
}
// 验证path是否为外链
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
// 验证参数是否为数组
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]';
}
return Array.isArray(arg);
}
// 验证参数是否为字符串类型
export function isString(str) {
if (typeof str === 'string' || str instanceof String) {
return true;
}
return false;
}
// 验证一个字符串是否只包含小写字母
export function validLowerCase(str) {
const reg = /^[a-z]+$/;
return reg.test(str);
}
// 验证一个字符串是否只包含大写字母
export function validUpperCase(str) {
const reg = /^[A-Z]+$/;
return reg.test(str);
}
// 验证一个字符串是否只包含英文字母
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/;
return reg.test(str);
}