表格类
1 构造者函数+基本功能
class ListDataCls {
/** 公共字段... **/
constructor(config) {
if (Reflect.has(config, 'isChart')) {
this.isChart = false; // 是否有 chart
this.charts = null; // 一般是 ChartDataCls 的实例,由外面赋值
}
deepAssign(this, config);
}
// 获取接口基本信息
async getBaseInfo(params, setting = {}) {}
// 请求页面数据
getListData() {}
// 获取数据后,表格数据处理
setBaseData(table, setting = {}) {}
// 前端分页 展示数据初始化
initFullData() {}
// 翻页
changePageNum(page) {}
// 每页大小改变
changePageSize(pageSize) {}
// 表格排序
changeTableSort(sortdata, tabledata) {}
// 表格复选
changeSelections(selection) {}
}
1. 引入常量
const TABLE_TIPS = {
default: '暂无数据',
loading: '数据查询中',
fail: '数据加载失败'
};
/** 接口数据状态码 */
const API_CODE = {
success: 1,
noPermission: 403
};
/**
* errSymbol 解析:
* 1 axios 的 quickPost 的弹窗
* 2 ListDataCls.dataList() 的报错
* 3
*/
const appTrackMessage = {
error(err, errSymbol) {
Message.error(`${err instanceof Error ? err.message : err} (错误码${errSymbol})`);
},
warning(err, errSymbol) {
Message.warning(`${err instanceof Error ? err.message : err} (警告码${errSymbol})`);
}
};
2. 公共字段
title = '';
permitted = true; // 是否有数据权限
tips = '暂无数据';
/** 接口 */
apis = { list: '' };
query = {
dataKey: 'list_data', // 返回的数据列表的key [list_data, data_list]
queryId: null, // 查询id,可用于帮助信息保存等
queryKey: ''
queryParams: {};
};
/** 帮助信息 */
remarks = {
enable: true,
editable: false, // 是否可编辑,从接口返回
/**
* 需要另外请求帮助信息
* 约定:为 true 时 queryRemark 只能是直接内容
*/
dynamic: false,
data: '' // 帮助信息的内容,直接内容或 json 内容
};
/** 表格 */
tables = {
visible: true, // 是否显示table
isSetColumns: false, // 是否请求完数据后调用 setColumns 方法
isIview: false, // 是否使用iview的表格
isFootOnTop: true, // footData 放在顶部
isFullData: false, // 前端分页相关,一般指后端一次性返回所有数据
meaningFieldsMap: {}, // 一行是数组型数据时,用这个来映射列(即表头)的含义。比如 `{ id: 1 }` 即 id 是第 2 列
fieldsSort: [] // 排序
};
columns = []; // 列信息(即表头)
data = []; // 全部数据
footData = []; // 脚部数据
showData = []; // 当前显示数据
selections = []; // 表格复选栏选中的数据
/** 表格排序 */
tableSort = {
isRemote: false, // 是否远程排序
order: '', // 升序、降序
which: null // 表示哪一列
};
/** 翻页 */
pages = {
enable: true,
pageType: null, // 暂时搁置,以前一般用于若等于 0 显示总数
current: 1, // 目前页码
size: 30, // 一页条目个数
sizeOptions: [10, 20, 30, 50, 100, 200, 500, 1000],
total: 0, // 总条目数
isShowTotal: true // 是否显示总数
};
2 事件处理程序
1. 对象属性合并
function deepAssign(target, ...sources) {
for (const source of sources) {
for (const key in source) {
if (Util.typeOf(target[key]) === 'object') {
deepAssign(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
2. 响应数据预处理
function _successResolve() {
return true;
}
3. 检查表格的填充条目是否数组
checkRawTableRowIsArray(data) {
for (let i in data) {
return Array.isArray(data[i]);
}
return false;
}
4. 使用iview的table && 单条表格数据是数组时,需变为对象
tableDataToObj(data, backData = []) {
data.forEach(item => {
let child = {};
item.forEach((item, index) => {
child['key' + index] = item;
});
backData.push(child);
});
return backData;
}
5. 字符串提取数字:(==='-' => 0,/^[0-9]%$/.test()
截取到%之前)
extractNumber(num) {
if (num === '-') {
return 0;
} else if (/(\d+)(%)/.test(String(num))) {
return parseFloat(num.substring(0, String(num).indexOf('%')));
} else if (/^(\d+)(~)(\d+)/.test(String(num))) {
return parseFloat(num.substring(0, String(num).indexOf('~')));
} else if (/^(\d+)(%)(~)(\d+)/.test(String(num))) {
return parseFloat(num.substring(0, String(num).indexOf('~')));
} else {
return num;
}
}
6. 字符串排序
function sortString(a, b) {
if (String(a) < String(b)) {
return -1;
} else if (String(a) > String(b)) {
return 1;
} else {
return 0;
}
}
3 处理应用程序逻辑具体实现
1. 获取接口基本信息
/**
* 获取接口基本信息,基本是查询类页面使用
* @param {object|undefined} params 请求参数
* @param {object} setting 其他设置
* @param {boolean} setting.isSetColumns 是否设置表头
*/
async getBaseInfo(params, setting = {}) {
const { isSetColumns } = setting;
let reqConfig = {};
params && (reqConfig.params = params);
const { data: result } = await $axios.get(this.apis.list, reqConfig).catch(err => {
Message.error('getApiInfo请求出错!');
throw err;
});
if (result.code === API_CODE.success) {
const table = result.data;
// ...一系列变量赋值
isSetColumns && this.setColumns(table.fields, table.fields_sort, this.meaningFieldsMap);
} else if (result.code === API_CODE.noPermission) {
this.isPermission = false;
throw new Error(result.msg);
} else {
Message.warning(result.msg);
throw new Error(result.msg);
}
return result;
}
2. 请求页面数据
/**
* - 用于把请求配置固定下来,非默认配置时自定义实例方法即可
* - 翻页默认调用此方法
*/
getListData(url, setting) {
return this.getListDataCore(url, setting);
}
/**
* 请求数据,包含了通用的业务处理
* @param {string} url 手动传入请求地址
* @param {object} setting 其他设置
* @param {boolean} setting.noSpin 没有全局 loading
* @param {boolean} setting.noVerify 表示不经过验证
* @param {boolean} setting.isSetColumns 覆盖 this.tables.isSetColumns
* @param {function} setting.successResolve 响应数据预处理
* @param {boolean} setting.onlyChartData 该次请求只处理 chart 数据
* @returns {promise}
*/
getListDataCore(url = this.apis.list, setting = {}) {
const { noVerify, noSpin } = setting;
// 请求配置
const requestConfig = { noSpin };
// 请求参数
let params = {
page_num: this.pages.current,
page_size: this.pages.size
};
if (this.tableSort.order) {
params.sort_type = this.tableSort.order;
params.sort_index = this.tableSort.which;
}
if (this.isChart) {
params.is_chart = 1;
}
Object.assign(params, this.query.queryParams)
// 请求前校验
if (!noVerify && !this.verify(params)) {
return Promise.reject(new Error('验证不通过'));
}
this.selections = []; // 清空选中
this.tips = TABLE_TIPS.loading; // 提示语
// 是图表
if (this.isChart) {
this.charts.setStatusCode(1);
}
return await getDataListFunc(url, params, requestConfig, setting)
}
async getDataListFunc(url, params, requestConfig, setting = {}) {
try {
let { data: result } = await $axios.post(url, params, requestConfig);
let isSecondaryError = result.code != API_CODE.success;
if (typeof result !== 'object') {
throw new Error('返回的数据格式错误!');
}
if (result.code === API_CODE.success) {
this.setListData(result, isSecondaryError, setting)
return result;
} else {
Message.warning(result.msg);
throw new Error(result.msg);
}
} catch(err) {
if (!$axios.isCancel(err)) {
appTrackMessage.error(err, 2);
}
throw err;
}
}
setListData(result, isSecondaryError, setting = {}) {
// 次级错误,成功请求接口了,但业务层面返回失败(code != successCode)
try {
let { successResolve = _successResolve } = setting
result.queryParams = params; // 把请求参数放进结果
this.tips = TABLE_TIPS.default;
this.charts.setStatusCode(2);
if (!successResolve(result)) {
return result;
}
let table = result.data; // table 相关数据
switch (true) {
case setting.onlyChartData:
table = this.charts.resolveChart(result);
break;
// chart处理
case this.isChart:
table = this.charts.resolveChart(result);
this.setBaseData(table, setting);
this.initFullData();
break;
// 前端分页处理
case this.tables.isFullData || table.is_full_data:
this.setBaseData(table, setting);
this.initFullData();
break;
// 斋表格
default:
this.setBaseData(table, setting);
this.setListShowData();
}
if (table.total_page > 1 && table[this.query.dataKey].length <= 0) {
Message.warning(result.msg);
}
return result;
} catch(err) {
if (isSecondaryError) {
this.tips = err.message;
} else {
this.tips = TABLE_TIPS.fail;
}
this.charts.setStatusCode(-1);
this.clearTable();
console.error(err);
}
}
clearTable() {
this.data = [];
this.footData = [];
this.showData = [];
this.overviews.fields = [];
this.overviews.data = [];
this.pages.total = 0;
}
3. 获取数据后,表格数据处理
setBaseData(table, setting = {}) {
const { isSetColumns = this.tables.isSetColumns } = setting;
const tableData = table[this.query.dataKey];
// ...一系列变量赋值
if (table.key_fields) {
this.meaningFieldsMap = table.key_fields;
}
// 设置表头
if (isSetColumns) {
this.setColumns(table.fields, table.fields_sort, this.meaningFieldsMap);
}
// 使用 iview table && 单条表格数据是数组时,需变为对象
if (this.tables.isIview && this.checkRawTableRowIsArray(tableData)) {
this.data = ListDataCls.tableDataToObj(tableData);
this.footData = ListDataCls.tableDataToObj(table.foot_data);
} else {
this.data = tableData;
this.footData = table.foot_data;
}
}
4. 前端分页 展示数据初始化
initFullData() {
if (this.pages.enable) {
this.updateFullShowData(1);
} else {
this.setListShowData();
}
}
updateFullShowData(pageNum = this.pages.current) {
const start = (pageNum - 1) * this.pages.size;
const end = pageNum * this.pages.size;
this.setListShowData(this.data.slice(start, end));
}
setListShowData(data = this.data, footData = this.footData) {
if (footData) {
if (this.tables.isFootOnTop) {
this.showData = footData.concat(data);
} else {
this.showData = data.concat(footData);
}
} else {
this.showData = data;
}
}
5. 分页操作
changePageNum(page) {
this.pages.current = page;
if (this.isChart || this.tables.isFullData) {
this.updateFullShowData();
} else {
this.getListData(undefined, { isSetColumns: false });
}
}
changePageSize(pageSize) {
this.pages.size = pageSize;
if (this.pages.current === 1) this.changePageNum(1);
}
6. 表格排序
tableSortChange(sortdata, tabledata) {
// TableInfinite和Table的sortdata有差异(index <==> key)
let sortIndex = Reflect.has(sortdata, 'index') ? sortdata.index : sortdata.key;
if (!tabledata || tabledata.length === 0) return;
if (
this.tableSort.isRemote ||
(!this.isChart && !this.tables.isFullData && this.tables.fieldsSort.length > 0)
) {
// 请求排序
this.sortTableRemotely(sortdata.order, sortIndex);
} else {
// 手动排序
this.sortTableHandle(tabledata, sortdata.order, soetIndex)
}
}
sortTableRemotely(order, sortIndex) {
this.pages.current = 1;
this.tableSort.order = order;
this.tableSort.which = sortIndex;
this.getListData(undefined, { isSetColumns: false });
}
sortTableHandle(tabledata, order, sortIndex) {
let orderType = order == 'asc' ? 1 : order == 'desc' ? -1 : 0,
isnum = 0;
if (!isNaN(tabledata[Math.floor(tabledata.length / 2)][sortIndex])) {
// 是数字
isnum++;
} else {
var reg = /(\d+)(%)/,
reg2 = /^(\d+)(~)(\d+)/,
reg3 = /^(\d+)(%)(~)(\d+)/;
for (var i = 0; i < tabledata.length; i++) {
if (
tabledata[i][sortIndex] === '-' ||
reg.test(tabledata[i][sortIndex]) ||
reg2.test(tabledata[i][sortIndex]) ||
reg3.test(tabledata[i][sortIndex])
) {
// 包含数字
isnum++;
break;
}
}
}
tableSortManually(orderType, tabledata, sortIndex, isnum);
if (this.isChart || this.tables.isFullData) {
this.pages.current = 1;
this.initFullData();
} else {
this.setListShowData();
}
}
/**
* 按类型排序
* @param {Number} type 排序类型asc[-1],desc[1],normal[0]
* @param {Array} data 数据 [[], []] or [{}, {}]
* @param {String | Number} sortIndex 表格排序列对应的key
* @param {Number} isnum 是否是数字排序(大于0数字,小于全部字符串,不设置时间排序)
*/
tableSortManually(type, data, sortIndex, isnum) {
switch (type) {
case -1:
isnum > 0
? data.sort((a, b) => extractNumber(a[sortIndex]) - extractNumber(b[sortIndex]))
: data.sort((a, b) => sortString(a[sortIndex], b[sortIndex]));
break;
case 1:
isnum > 0
? data.sort((a, b) => extractNumber(b[sortIndex]) - extractNumber(a[sortIndex]))
: data.sort((a, b) => sortString(b[sortIndex], a[sortIndex]));
break;
default:
return false;
}
}
7. 表格复选
changeSelections(selections) {
this.selections = selections;
}
8. 清空表格数据
clearTable() {
this.data = [];
this.footData = [];
this.showData = [];
this.overviews.fields = [];
this.overviews.data = [];
this.pages.total = 0;
}
9. 重置表格排序
resetTableSort() {
this.pages.current = 1;
this.tableSort.order = '';
this.tableSort.which = null;
}
10. 应由外面赋值的方法
setColumns() {
console.warn('缺少自定义的 setColumns 方法');
}
// params 请求参数
setQueryParam(params) {
return params;
}
verify() {
return true;
}
/**
* 设置下载参数
* @param {object} params 接口参数
* @param {object} setting 其他配置
* @param {boolean} setting.noVerify 表示不经过验证
*/
setDownloadInfo(params, setting) {
const { noVerify } = setting;
// 若需要验证
if (!noVerify && !this.verify(params)) {
params.IS_STOP_DOWNLOAD = true;
return;
}
this.setQueryParam(params);
}