',
+ timestamp: '2018-04-15'
+ }, {
+ content: '通过审核',
+ timestamp: '2018-04-13'
+ }, {
+ content: '创建成功',
+ timestamp: '2018-04-11'
+ }],
+ operations: {
+ reverseSelect: {
+ slot: 'header',
+ type: 'field',
+ field: {
+ type: 'radio',
+ default: 1,
+ props: {
+ options: [
+ { label: '正叙', value: 1 },
+ { label: '倒叙', value: 0 }
+ ]
+ }
+ }
+ }
+ },
+ actions: {
+ fieldChange({ path, value }) {
+ if (path === 'header.reverseSelect') {
+ this.block.props.reverse = Boolean(value);
+ }
+ }
+ }
+ },
+ timeline2Title: {
+ type: 'title',
+ options: {
+ title: '自定义节点'
+ }
+ },
+ timeline2Block: {
+ type: 'timeline',
+ data: [{
+ content: '支持使用图标',
+ timestamp: '2018-04-12 20:46',
+ size: 'large',
+ type: 'primary',
+ icon: 'el-icon-more'
+ }, {
+ content: '支持自定义颜色',
+ timestamp: '2018-04-03 20:46',
+ color: '#0bbd87'
+ }, {
+ content: '支持自定义尺寸',
+ timestamp: '2018-04-03 20:46',
+ size: 'large'
+ }, {
+ content: '默认样式的节点',
+ timestamp: '2018-04-03 20:46'
+ }]
+ }
+ }
+});
\ No newline at end of file
diff --git a/examples/router/src/ams-config/cases-field/select.js b/examples/router/src/ams-config/cases-field/select.js
index c4830bd9..de5e6ca8 100644
--- a/examples/router/src/ams-config/cases-field/select.js
+++ b/examples/router/src/ams-config/cases-field/select.js
@@ -149,9 +149,7 @@ ams.block('select', {
queryKey: 'requiredName',
labelKey: 'name',
params: {
- params: {
- a: 2
- }
+ a: 2
},
isCache: false,
transform($field, data) {
diff --git a/examples/router/src/ams-config/index.js b/examples/router/src/ams-config/index.js
index 30af0a27..3e324401 100644
--- a/examples/router/src/ams-config/index.js
+++ b/examples/router/src/ams-config/index.js
@@ -49,6 +49,7 @@ import './cases-block/list/list-pagination';
import './cases-block/list/list-drag';
import './cases-block/list/list-field-convert';
import './cases-block/list/list-simulate';
+import './cases-block/list/list-tab';
import './cases-block/alert';
import './cases-block/calendar';
import './cases-block/dragimage';
@@ -71,4 +72,5 @@ import './cases-block/title';
import './cases-block/drawer';
import './cases-block/table';
import './cases-block/form/edit';
-import './cases-block/form/view';
\ No newline at end of file
+import './cases-block/form/view';
+import './cases-block/timeline';
\ No newline at end of file
diff --git a/examples/router/src/ams-config/router.js b/examples/router/src/ams-config/router.js
index fa775616..dc79d868 100644
--- a/examples/router/src/ams-config/router.js
+++ b/examples/router/src/ams-config/router.js
@@ -38,6 +38,7 @@ ams.block('router', {
}
},
router: {
+ // keepAlive: true, // 设置为true的话,所有菜单都会keepAlive
// mode: 'history',
// base: '/app/', // 要在history下才会发挥作用
// showMenu: false, // 隐藏所有菜单配置项,默认为true
@@ -47,7 +48,7 @@ ams.block('router', {
// textColor: '#bfcbd9', // 菜单的文字颜色(仅支持 hex 格式),默认值:#bfcbd9
// activeTextColor: '#409EFF', // 菜单的文字颜色(仅支持 hex 格式),默认值:#409EFF
// defaultBreadcrumb: false, // 默认为true,会把首个route作为默认首页加到所有面包屑,设置为false禁用此行为, 0.7.5+支持
- // shwoBreadcrumb: false, // 默认为true,是否显示面包屑
+ // showBreadcrumb: false, // 默认为true,是否显示面包屑
routes: [
{
name: '首页',
@@ -57,6 +58,9 @@ ams.block('router', {
// redirect: '/list',
meta: {
icon: 'el-icon-menu'
+ },
+ tooltip: {
+ content: '首页提示文字'
}
},
{
@@ -232,6 +236,11 @@ ams.block('router', {
name: '前端分页',
path: 'list-simulate',
block: 'list-simulate'
+ },
+ {
+ name: 'tab中的list',
+ path: 'list-tab',
+ block: 'list-tab'
}
]
},
@@ -369,6 +378,11 @@ ams.block('router', {
name: 'Alert告警',
path: 'alert',
block: 'alert'
+ },
+ {
+ name: '时间线',
+ path: 'timeline',
+ block: 'timeline'
}
]
},
diff --git a/package.json b/package.json
index a81a9e2b..e5d51fa1 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"vue",
"element-ui"
],
- "version": "0.33.0",
+ "version": "0.34.5",
"main": "lib/ams.common.js",
"unpkg": "lib/ams.js",
"scripts": {
diff --git a/packages/block-xlsx/package.json b/packages/block-xlsx/package.json
index 4dc63915..e1f25414 100644
--- a/packages/block-xlsx/package.json
+++ b/packages/block-xlsx/package.json
@@ -1,6 +1,6 @@
{
"name": "@ams-team/block-xlsx",
- "version": "0.9.3",
+ "version": "0.9.4",
"description": "结合ams的resources配置使用的导出导出xlsx插件",
"author": "zebin.wu <1069467662@qq.com>",
"main": "lib/block-xlsx.js",
diff --git a/packages/block-xlsx/src/xlsx/reader.ts b/packages/block-xlsx/src/xlsx/reader.ts
new file mode 100644
index 00000000..389b7b9e
--- /dev/null
+++ b/packages/block-xlsx/src/xlsx/reader.ts
@@ -0,0 +1,314 @@
+import {
+ RawCell, LiteralCell, NotEmptyCell, Dict, Dicts, Data, Opts, WorkbookReaderError, ImportHeader,
+ XlsxPopulateWorkbook, XlsxPopulateWorksheet, XlsxPopulateRichTextCell
+} from './types';
+import { Combiner } from './combiner';
+import { dateParse, formatDate } from '../utils/date';
+
+export const reverseDict = (dict: Dict): Dict => Object.keys(dict).reduce((newDict, key) => {
+ newDict[dict[key]] = key;
+ return newDict;
+},
{});
+
+export function numberValidator(value: LiteralCell, min?: any, max?: any, toFixedNumber?: any) {
+ if (typeof value === 'number') {
+ const valueToNumber = Number(value);
+ if (min !== undefined && min > valueToNumber) return `应大于等于${min}`;
+ if (max !== undefined && max < valueToNumber) return `应小于等于${max}`;
+ if (toFixedNumber) {
+ return true;
+ }
+ return true;
+ }
+ return '应为数字类型';
+}
+
+export function integerValidator(value: LiteralCell, min?: any, max?: any) {
+ const isNumber = numberValidator(value);
+ if (typeof isNumber === 'string') return isNumber;
+ if (Number.isInteger(value)) {
+ const valueToNumber = Number(value);
+ if (min !== undefined && min > valueToNumber) return `应大于等于${min}`;
+ if (max !== undefined && max < valueToNumber) return `应小于等于${max}`;
+ return true;
+ }
+ return '应为整数';
+}
+
+export const nonZeroNumberFilter = (v: any): (any | '') => (!isNaN(Number(v)) && Number(v) !== 0 ? v : '');
+
+export const valueToString = (value: RawCell): string => {
+ switch (typeof value) {
+ case 'number':
+ case 'string':
+ return (value).toString();
+ default:
+ return JSON.stringify(value);
+ }
+}
+export function valueToDate(value: RawCell) {
+ switch (typeof value) {
+ case 'number': {
+ const num = (value) - 1; // 这里就当作所有日期都在 bug day 1900-02-29 之后
+ // const dateBase = new Date(1900, 0, 0); // chrome不同版本计算出来的数据不一致,直接固定写死一个数值,后续排查
+ const dateBase = -2209104000000;
+ const secondsInDay = 60 * 60 * 24;
+ return new Date(Math.round(num * secondsInDay) * 1000 + dateBase);
+ }
+ case 'string': return dateParse(value);
+ default: return new Date(NaN);
+ }
+}
+export const valueToSeconds = (value: RawCell) => {
+ const date = valueToDate(value);
+ if (isNaN(date.getTime())) {
+ throw new Error('日期格式错误');
+ }
+ return Math.round(date.getTime() / 1000);
+};
+export const valueToDateString = (format: string) => (value: RawCell) => {
+ const date = valueToDate(value);
+ if (isNaN(date.getTime())) {
+ throw new Error('日期格式错误');
+ }
+ return formatDate(date, format);
+}
+
+export const oaValidator = (value: LiteralCell) => {
+ if (typeof value === 'string') {
+ const result = /^([^/]*\/)?[a-zA-Z0-9.]+$/.exec(value);
+ if (result && result[0] === value) return true;
+ }
+ return '请填写正确的 OA,如「张三/san.zhang」或「san.zhang」';
+};
+
+/**
+ * @param {Array<{ name: 'r', children: Array<{ name: 't', children: string }> }>} cell
+ */
+function richTextCellToString(cell: XlsxPopulateRichTextCell) {
+ if (Array.isArray(cell)) {
+ return cell
+ .filter(node => node.name === 'r') // => rNodes
+ .map(rNode => rNode.children.filter(child => child.name === 't')) // => Array>
+ .map(tNodes => tNodes.map(tNode => {
+ const v = tNode.children[0];
+ if (v || `${v}` === '0') return v;
+ return ''
+ }).join('')) // => Array
+ .join('');
+ }
+ return '';
+}
+
+export abstract class WorkbookReader {
+ errors: WorkbookReaderError[] = [];
+ result: object[] = [];
+ protected abstract headers: ImportHeader[] = [];
+ protected readonly rowStartAt: number = 1;
+ protected readonly abstract combiner: Combiner;
+ protected abstract opts: Opts;
+ protected sheet: XlsxPopulateWorksheet;
+
+ read(wb: XlsxPopulateWorkbook): void {
+ this.sheet = this.getSheet(wb);
+ if (this.isSheetEmpty) {
+ throw new Error('导入文件中无数据');
+ }
+ const rows: RawCell[][] = this.getRows(this.sheet);
+ this.preCheck(rows, this.opts);
+ if (this.hasError) return;
+
+ let count = this.rowStartAt;
+ const items = this.getGroups(rows).map((group) => {
+ const preCount = count;
+ count += group.length;
+ return this.parseGroup(group, preCount);
+ });
+ this.result = this.postParse(items, this.opts);
+
+ if (this.hasError) return;
+ this.postCheck(this.result, this.opts);
+ if (this.hasError) return;
+ }
+
+ /**
+ * 在逐行解析前进行一次检查,执行后如果 this.errors 有项,则不再进行解析
+ */
+ protected preCheck(rows: RawCell[][], opts: Opts): void {}
+ /**
+ * 数据解析之后的最后校验
+ */
+ protected postCheck(items: object[], opts: Opts): void {}
+
+ protected postParse(items: object[], opts: Opts): object[] {
+ return items;
+ }
+
+ get hasError() {
+ return this.errors.length !== 0;
+ }
+
+ protected getSheet(wb: XlsxPopulateWorkbook): XlsxPopulateWorksheet {
+ return wb.sheet(0);
+ }
+ private getRows(sheet: XlsxPopulateWorksheet): RawCell[][] {
+ const rowLength = sheet.usedRange()._maxRowNumber;
+
+ let rows = sheet.range(Math.max(this.rowStartAt + 1, 1), 1, rowLength, this.headers.length).value();
+ // 若某一行为空行,则不再解析,但要检测以确保剩余行均不为空行
+ const blankRowIndex = rows.findIndex((row) => row.every(WorkbookReader.isCellEmpty))
+ if (blankRowIndex !== -1) {
+ if (blankRowIndex === 0) {
+ throw new Error(`第 ${this.rowStartAt + 1} 行为空行,请检查`);
+ }
+ for (let i = blankRowIndex + 1; i < rows.length; i++) {
+ if (rows[i].some(v => !WorkbookReader.isCellEmpty(v))) {
+ throw new Error(`第 ${this.rowStartAt - 1 + i} 行为空行,请检查`);
+ }
+ }
+ return rows.slice(0, blankRowIndex);
+ }
+ return rows;
+ }
+ private getGroups(rows: RawCell[][]): RawCell[][][] {
+ let groups: RawCell[][][] = [];
+ let curGroupsStartAt = this.rowStartAt;
+ const errMsg = (msg: string, i: number) => `第 ${curGroupsStartAt + 1} ~ ${i - 1 + this.rowStartAt + 1} 行:${msg}`;
+ // rows.slice(this.rowStartAt).forEach((row, i) => {
+ rows.slice(0).forEach((row, i) => { // 因为在getRows中已经处理了rows = sheet.range(this.rowStartAt + 1)
+ let result: RawCell[][][];
+ try {
+ result = this.combiner.push(row);
+ } catch (e) {
+ throw new Error(errMsg(e.message, i));
+ }
+ // 判断之前的行是否已被分组
+ if (result.length > 0) {
+ curGroupsStartAt = this.rowStartAt + i;
+ }
+ });
+ try {
+ this.combiner.end();
+ } catch (e) {
+ throw new Error(errMsg(e.message, rows.length - 1));
+ }
+ return this.combiner.result;
+ }
+ private parseGroup(group: RawCell[][], groupStartAt: number): object {
+ const item = this.parseRow(group[0], false, groupStartAt);
+ const multirows: object[] = [];
+ group.forEach((row, i) => {
+ multirows.push(this.parseRow(row, true, groupStartAt + i));
+ });
+ this.assignMultirowAndRemoveRowIndex(item, multirows);
+ return item;
+ }
+ protected assignMultirowAndRemoveRowIndex(item: object, multirows: object[]): void {}
+ private parseRow(richTextRow: RawCell[], multiRow: boolean, rowIndex: number): object {
+ const parsed: { [field: string]: any, __rowIndex: number } = { __rowIndex: rowIndex };
+ const row: LiteralCell[] = richTextRow.map(cell => (typeof cell === 'object' ? richTextCellToString(cell) : cell))
+ const rowDict: Data = row
+ .map((item, i) => ({ [this.headers[i] ? this.headers[i].field : '']: item }))
+ .reduce((obj, cur) => Object.assign(obj, cur), {})
+ this.headers.forEach((header, i) => {
+ let value = row[i];
+
+ // 判断是否忽略
+ if (
+ (typeof header.ignored === 'function' ? header.ignored(value, rowDict, row) : header.ignored)
+ || (!multiRow && header.multirow)
+ || (multiRow && !header.multirow)
+ ) return;
+
+ const pos = `${WorkbookReader.toColumnName(i + 1)}${rowIndex + 1}(${header.label})`;
+ // 检查必填项
+ const required = typeof header.required === 'function'
+ ? header.required(value, rowDict, row)
+ : header.required;
+ if (required && WorkbookReader.isCellEmpty(value)) {
+ this.addError(pos, '请填写必填项');
+ return;
+ }
+ if (header.trim !== false && typeof value === 'string') {
+ value = value.replace(/(^\s*)|(\s*$)/g, '');
+ }
+ // 调用 validate
+ if (header.validate && !WorkbookReader.isCellEmpty(value)) {
+ let result;
+ try {
+ result = header.validate(value, rowDict, row);
+ } catch (e) {
+ result = e.message;
+ }
+ if (typeof result === 'string') {
+ this.addError(pos, result);
+ return;
+ }
+ }
+ // 转换
+ if (header.preConvert && !WorkbookReader.isCellEmpty(value)) {
+ value = header.preConvert(value);
+ }
+ if (header.dict && !WorkbookReader.isCellEmpty(value)) {
+ value = String(value);
+ const dict = typeof header.dict === 'function' ? header.dict(value, rowDict, row) : header.dict;
+ if (typeof header.split === 'string') {
+ const failures = value.split(header.split).filter(v => !dict.hasOwnProperty(v));
+ if (failures.length > 0) {
+ this.addError(pos, `${failures.map(v => `「${v}」`).join('、')}不存在`);
+ return;
+ } else {
+ parsed[header.field] = value.split(header.split).map(v => dict[v]);
+ if (typeof header.join === 'string') {
+ parsed[header.field] = parsed[header.field].join(header.join);
+ }
+ }
+ } else {
+ if (!dict.hasOwnProperty(value)) {
+ this.addError(pos, `「${value}」不存在`);
+ return;
+ } else {
+ parsed[header.field] = dict[value];
+ }
+ }
+ } else if (header.convert) {
+ if (WorkbookReader.isCellEmpty(value) && !header.convertIfEmpty) return;
+ try {
+ parsed[header.field] = header.convert(value, rowDict, row, rowIndex);
+ } catch (e) {
+ this.addError(pos, e.message);
+ return;
+ }
+ } else {
+ parsed[header.field] = WorkbookReader.isCellEmpty(value) ? '' : value;
+ }
+ });
+ return parsed;
+ }
+ private get isSheetEmpty() {
+ return this.sheet.usedRange()._maxRowNumber < (this.rowStartAt + 1);
+ }
+
+ protected addError(pos: string, message: string) {
+ this.errors.push({ pos, message });
+ }
+
+ static isCellEmpty(value: RawCell): boolean {
+ return typeof value === 'undefined'
+ || (typeof value === 'string' && value.length === 0);
+ }
+
+ /**
+ * 将列数转为对应的名。e.g.: 29 -> 'AC'
+ * @param {number} num 1-based
+ * @return {string}
+ */
+ static toColumnName(num: number): string {
+ let result = '';
+ for (let a = 1, b = 26; num >= a; a = b, b *= 26) {
+ num -= a;
+ result = String.fromCharCode(Math.floor((num % b) / a) + 65) + result;
+ }
+ return result;
+ }
+}
diff --git a/packages/field-tag/package.json b/packages/field-tag/package.json
index d26d5006..6077c56f 100644
--- a/packages/field-tag/package.json
+++ b/packages/field-tag/package.json
@@ -1,6 +1,6 @@
{
"name": "@ams-team/field-tag",
- "version": "1.2.7",
+ "version": "1.2.9",
"description": "AMS标签",
"author": "ams",
"main": "lib/field-tag.js",
diff --git a/packages/field-tag/src/edit.vue b/packages/field-tag/src/edit.vue
index 9c999675..f63f7b7f 100644
--- a/packages/field-tag/src/edit.vue
+++ b/packages/field-tag/src/edit.vue
@@ -6,6 +6,7 @@
:key="index"
v-on="on"
v-bind="field.props"
+ @click="on && on.click(tag)"
@close="handleDelete(tag)">
{{ field.props.template ? field.props.template.replace(idRegExp, tag[field.props['idKey']] || '').replace(nameRegExp, tag[field.props['nameKey']] || '').replace(subNameRegExp, tag[field.props['subNameKey']] || '') : (tag[field.props['nameKey']] || tag) }}
@@ -218,9 +219,13 @@ export default {
// pageSize: 20
};
if (item) {
- params.data[this.field.props['idKey']] = item;
- params.data[this.field.props['nameKey']] = item;
- params.data[this.field.props['subNameKey']] = item;
+ if (this.field.props['queryKey']) {
+ params.data[this.field.props['queryKey']] = item;
+ } else {
+ params.data[this.field.props['idKey']] = item;
+ params.data[this.field.props['nameKey']] = item;
+ params.data[this.field.props['subNameKey']] = item;
+ }
}
} else {
params.params = {
@@ -228,9 +233,13 @@ export default {
// pageSize: 20
};
if (item) {
- params.params[this.field.props['idKey']] = item;
- params.params[this.field.props['nameKey']] = item;
- params.params[this.field.props['subNameKey']] = item;
+ if (props['queryKey']) {
+ params.params[props['queryKey']] = item;
+ } else {
+ params.params[props['idKey']] = item;
+ params.params[props['nameKey']] = item;
+ params.params[props['subNameKey']] = item;
+ }
}
}
const resource = ams.resources[ams.blocks[this.name].resource];
diff --git a/plugins/vscode/snippets/curd.json b/plugins/vscode/snippets/curd.json
index 34c491d5..ed3db47c 100644
--- a/plugins/vscode/snippets/curd.json
+++ b/plugins/vscode/snippets/curd.json
@@ -11,7 +11,7 @@
" ",
" ",
" ",
- " ",
+ " ",
" ",
" ",
"
\ No newline at end of file
diff --git a/src/fields/array/array-edit.vue b/src/fields/array/array-edit.vue
index 1a602097..7d11ec42 100644
--- a/src/fields/array/array-edit.vue
+++ b/src/fields/array/array-edit.vue
@@ -38,13 +38,14 @@
{{field.field.label && field.field.label + (index + 1)}}
-