提交 90a10cf9 编写于 作者: ibizdev's avatar ibizdev

chitanda 发布系统代码 [TrainSys,网页端]

上级 5207173f
......@@ -12,16 +12,17 @@
},
"dependencies": {
"@ibiz-template/command": "^0.0.1-alpha.37",
"@ibiz-template/controller": "^0.0.1-alpha.38",
"@ibiz-template/core": "^0.0.1-alpha.38",
"@ibiz-template/model": "^0.0.1-alpha.37",
"@ibiz-template/runtime": "^0.0.1-alpha.38",
"@ibiz-template/service": "^0.0.1-alpha.38",
"@ibiz-template/theme": "^0.0.1-alpha.37",
"@ibiz-template/vue-util": "^0.0.1-alpha.38",
"@ibiz-template/controller": "^0.0.1-alpha.40",
"@ibiz-template/core": "^0.0.1-alpha.40",
"@ibiz-template/model": "^0.0.1-alpha.39",
"@ibiz-template/runtime": "^0.0.1-alpha.40",
"@ibiz-template/service": "^0.0.1-alpha.40",
"@ibiz-template/theme": "^0.0.1-alpha.40",
"@ibiz-template/vue-util": "^0.0.1-alpha.40",
"@ibiz/dynamic-model-api": "^2.1.5",
"dayjs": "^1.11.6",
"lodash-es": "^4.17.21",
"path-browserify": "^1.0.1",
"pinia": "^2.0.23",
"qs": "^6.11.0",
"qx-util": "^0.4.4",
......@@ -31,15 +32,16 @@
"vue-router": "^3.6.5"
},
"devDependencies": {
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@commitlint/cli": "^17.2.0",
"@commitlint/config-conventional": "^17.2.0",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.11.8",
"@types/node": "^18.11.9",
"@types/path-browserify": "^1.0.0",
"@types/qs": "^6.9.7",
"@types/ramda": "^0.28.18",
"@types/systemjs": "^6.1.1",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"@vitejs/plugin-legacy": "^2.3.0",
"@vitejs/plugin-vue2": "^2.0.0",
"@vitejs/plugin-vue2-jsx": "^1.0.3",
......@@ -49,7 +51,7 @@
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.6.0",
"eslint-plugin-vue": "^9.7.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"postcss": "^8.4.18",
......@@ -65,7 +67,7 @@
"stylelint-scss": "^4.3.0",
"terser": "^5.15.1",
"typescript": "^4.8.4",
"vite": "^3.2.1",
"vite": "^3.2.2",
"vite-plugin-eslint": "^1.8.1",
"vue-eslint-parser": "^9.1.0",
"vue-tsc": "^1.0.9"
......
此差异已折叠。
......@@ -5,7 +5,6 @@ import { install as installService } from '@ibiz-template/service';
import { install as installRuntime } from '@ibiz-template/runtime';
import { install as installController } from '@ibiz-template/controller';
import { IViewRegister } from './i-view-register';
import UserRegister from './user-register';
import {
AppLayout,
ControlLayout,
......@@ -57,6 +56,8 @@ import {
} from './components/views';
import { IndexView } from './views';
import AppKeepAlive from './components/common/app-keep-alive/app-keep-alive.vue';
import { presetAllProviders } from './provider';
import { ViewShell } from './components/view-shell/view-shell';
import {
AppIcon,
ViewToolbar,
......@@ -72,27 +73,28 @@ import {
WfVersionSelect,
ExtendActionTimeLine,
ExtendActionGrid,
AppSelectTree,
} from './components/common';
// 编辑器组件
import IBizSpan from './components/editor/ibiz-span/ibiz-span';
import IBizInputBox from './components/editor/ibiz-input-box/ibiz-input-box';
import AppInputNumber from './components/editor/app-input-number/app-input-number';
import IBizCheckBoxList from './components/editor/ibiz-check-box-list/ibiz-check-box-list';
import IBizRadioButtonList from './components/editor/ibiz-radio-button-list/ibiz-radio-button-list';
import IBizDatePicker from './components/editor/ibiz-date-picker/ibiz-date-picker';
import IBizDropDownList from './components/editor/ibiz-dropdown-list/ibiz-dropdown-list';
import IBizPicker from './components/editor/ibiz-picker/ibiz-picker';
import AppSelectTree from './components/editor/app-select-tree/app-select-tree';
import IBizPickerDropDown from './components/editor/ibiz-picker-dropdown/ibiz-picker-dropdown';
import AppPickerLinkOnly from './components/editor/app-picker-linkonly/app-picker-linkonly';
import IBizMPicker from './components/editor/ibiz-mpicker/ibiz-mpicker';
import IBizFileUpload from './components/editor/ibiz-file-upload/ibiz-file-upload';
import AppFileUploadRowPreview from './components/editor/app-file-upload-row-preview/app-file-upload-row-preview';
import AppImageUpload from './components/editor/app-image-upload/app-image-upload';
import NotSupportedEditor from './components/editor/not-supported-editor/not-supported-editor';
import { presetAllProviders } from './provider';
import { ViewShell } from './components/view-shell/view-shell';
import {
IBizGridFileUpload,
IBizImageUpload,
IBizInputNumber,
IBizPickerLink,
IBizCheckbox,
IBizDatePicker,
IBizDropDownList,
IBizFileUpload,
IBizInput,
IBizMPicker,
IBizPicker,
IBizPickerDropDown,
IBizRadio,
IBizSpan,
NotSupportedEditor,
IBizGridInput,
IBizGridCheckbox,
} from './components/editor';
export const AppRegister = {
install(v: VueConstructor) {
......@@ -166,25 +168,26 @@ export const AppRegister = {
v.component('WfVersionSelect', WfVersionSelect);
v.component('ExtendActionTimeLine', ExtendActionTimeLine);
v.component('ExtendActionGrid', ExtendActionGrid);
v.component('AppSelectTree', AppSelectTree);
// 注册编辑器组件
v.component('IBizSpan', IBizSpan);
v.component('IBizInputBox', IBizInputBox);
v.component('AppInputNumber', AppInputNumber);
v.component('IBizCheckBoxList', IBizCheckBoxList);
v.component('IBizRadioButtonList', IBizRadioButtonList);
v.component('IBizInput', IBizInput);
v.component('IBizInputNumber', IBizInputNumber);
v.component('IBizCheckbox', IBizCheckbox);
v.component('IBizRadio', IBizRadio);
v.component('IBizDatePicker', IBizDatePicker);
v.component('IBizDropDownList', IBizDropDownList);
v.component('IBizPicker', IBizPicker);
v.component('IBizPickerDropDown', IBizPickerDropDown);
v.component('AppPickerLinkOnly', AppPickerLinkOnly);
v.component('IBizPickerLink', IBizPickerLink);
v.component('IBizMPicker', IBizMPicker);
v.component('IBizFileUpload', IBizFileUpload);
v.component('AppFileUploadRowPreview', AppFileUploadRowPreview);
v.component('AppImageUpload', AppImageUpload);
v.component('AppSelectTree', AppSelectTree);
v.component('IBizGridFileUpload', IBizGridFileUpload);
v.component('IBizImageUpload', IBizImageUpload);
v.component('QuickSearch', QuickSearch);
v.component('NotSupportedEditor', NotSupportedEditor);
// 扩展注册
v.use(UserRegister);
// 注册表格编辑器
v.component('IBizGridInput', IBizGridInput);
v.component('IBizGridCheckbox', IBizGridCheckbox);
},
};
import { Ref, ref, watch, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
export const AppSelectTree = defineComponent({
name: 'AppSelectTree',
props: {
value: {
type: String as PropType<Array<string> | string>,
},
multiple: {
type: Boolean,
},
nodesData: {
type: Array as PropType<IData[]>,
},
},
setup(props) {
const ns = useNamespace('app-select-tree');
const visible = ref(false);
const treeNodes: Ref<IData[]> = ref([]);
const handletreeNodes = (nodes: readonly IData[]) => {
if (nodes.length === 0) {
return [];
}
const list: IData[] = [];
nodes.forEach((codeItem: IData) => {
const tempObj: IData = {
title: codeItem.text,
value: codeItem.value,
children: [],
};
if (codeItem.children && codeItem.children.length > 0) {
tempObj.children = handletreeNodes(codeItem.children);
}
list.push(tempObj);
});
return list;
};
watch(
() => props.nodesData,
(newVal, oldVal) => {
if (newVal !== oldVal) {
treeNodes.value = handletreeNodes(newVal!);
}
},
{ immediate: true },
);
const triggerMenu = (_visible: boolean) => {
if (!_visible) {
visible.value = !visible.value;
} else {
visible.value = _visible;
}
};
return { ns, visible, treeNodes, triggerMenu };
},
render() {
return (
<div class={this.ns.b()}>
<i-dropdown
visible={this.visible}
trigger='custom'
transfer-class-name={this.ns.e('dropdown')}
transfer
on-on-clickoutside={() => {
this.triggerMenu(false);
}}
>
<div
on-click={() => {
this.triggerMenu(true);
}}
>
打开下拉树
</div>
<i-dropdownMenu slot='list'>
<i-tree data={this.treeNodes} show-checkbox></i-tree>
</i-dropdownMenu>
</i-dropdown>
</div>
);
},
});
......@@ -13,6 +13,7 @@ import { WfVersionSelect } from './wf-version-select/wf-version-select';
import { ExtendActionTimeLine } from './extend-action-timeline/extend-action-timeline';
import { ExtendActionGrid } from './extend-action-grid/extend-action-grid';
export { AppSelectTree } from '../common/app-select-tree/app-select-tree';
export {
AppIcon,
ViewToolbar,
......
import { computed, defineComponent, ref, watch } from 'vue';
import {
getCheckboxProps,
getEditorEmits,
useNamespace,
} from '@ibiz-template/vue-util';
import { isNil } from 'ramda';
import '@ibiz-template/theme/style/components/editor/ibiz-checkbox/ibiz-checkbox.scss';
export const IBizCheckbox = defineComponent({
name: 'IBizCheckbox',
props: getCheckboxProps(),
emits: getEditorEmits(),
setup(props, { emit }) {
const ns = useNamespace('checkbox');
const c = props.controller;
const editorModel = c.model;
const codeList = editorModel.codeList;
// 代码表数据
const items = ref<readonly IData[]>([]);
watch(
() => props.data,
newVal => {
c.loadCodeList(newVal).then(_codeList => {
items.value = _codeList;
});
},
{
immediate: true,
deep: true,
},
);
// 当前模式
const currentMode = computed(() => {
if (codeList && codeList.orMode) {
return codeList.orMode.toLowerCase();
}
return 'str';
});
// 值分隔符
let valueSeparator = ',';
if (codeList && codeList.valueSeparator) {
valueSeparator = codeList.valueSeparator;
}
// 选中数组
const selectArray = computed({
get() {
if (!isNil(props.value)) {
if (Object.is(currentMode.value, 'num') && items) {
const selectsArray: Array<string | number> = [];
const num: number =
typeof props.value === 'string'
? parseInt(props.value, 10)
: props.value;
items.value.forEach((item: IData) => {
// eslint-disable-next-line no-bitwise
if ((num & item.value) === item.value) {
selectsArray.push(item.value);
}
});
return selectsArray;
}
if (Object.is(currentMode.value, 'str')) {
const strVal = props.value! as String;
if (strVal !== '') {
if (codeList) {
const selects: Array<string | number> =
strVal.split(valueSeparator);
if (codeList.codeItemValueNumber) {
for (let i = 0, len = selects.length; i < len; i++) {
selects[i] = Number(selects[i]);
}
}
return selects;
}
return strVal.split(',');
}
}
}
return [];
},
set(val: Array<string | number>) {
let value: null | string | number | string[] = null;
if (Object.is(currentMode.value, 'num')) {
let temp: number = 0;
val.forEach(item => {
const numVal: number =
typeof item === 'string' ? parseInt(item, 10) : item;
// eslint-disable-next-line no-bitwise
temp |= numVal;
});
value = temp;
} else if (Object.is(currentMode.value, 'str')) {
const _datas: string[] = [];
if (items.value.length > 0) {
items.value.forEach((_item: IData) => {
const index = val.findIndex((_key: unknown) =>
Object.is(_item.value, _key),
);
if (index === -1) {
return;
}
_datas.push(_item.value);
});
value = _datas.join(valueSeparator);
}
}
emit('change', value);
},
});
const onSelectArrayChange = (value: Array<string | number>) => {
selectArray.value = value;
};
return {
ns,
items,
selectArray,
onSelectArrayChange,
};
},
render() {
return (
<div class={this.ns.b()}>
{this.infoMode ? (
this.value
) : (
<i-checkbox-group
value={this.selectArray}
on-on-change={this.onSelectArrayChange}
>
{this.items.map((_item, index: number) => (
<i-checkbox
key={index}
label={_item.value}
readonly={this.controller.model.readOnly}
>
<span class={this.ns.e('text')}>{_item.text}</span>
</i-checkbox>
))}
</i-checkbox-group>
)}
</div>
);
},
});
import { defineComponent } from 'vue';
import {
getGridInputProps,
useGridCellEditor,
useNamespace,
} from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-input/ibiz-input.scss';
export const IBizGridCheckbox = defineComponent({
name: 'IBizGridCheckbox',
props: getGridInputProps(),
setup(props) {
const ns = useNamespace('grid-checkbox');
const { isInfoMode, componentRef, onOperateChange, onChange } =
useGridCellEditor(props.rowDataChange, {
isCacheValue: true,
});
return {
ns,
isInfoMode,
componentRef,
onOperateChange,
onChange,
};
},
render(h) {
return (
<div
ref='componentRef'
class={`${this.ns.b()} ibiz-grid-editor`}
onDblclick={evt => evt.stopPropagation()}
onClick={evt => evt.stopPropagation()}
>
{h('IBizCheckbox', {
props: {
...this.$props,
infoMode: this.isInfoMode,
},
on: {
change: this.onChange,
operate: this.onOperateChange,
},
})}
</div>
);
},
});
export { IBizCheckbox } from './ibiz-checkbox/ibiz-checkbox';
export { IBizGridCheckbox } from './ibiz-grid-checkbox/ibiz-grid-checkbox';
import { PickerEditorController } from '@ibiz-template/controller';
import { ref, watch, Ref, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-mpicker/ibiz-mpicker.scss';
export const IBizMPicker = defineComponent({
name: 'IBizMPicker',
props: {
value: {
type: String,
},
controller: {
type: PickerEditorController,
},
data: {
type: Object as PropType<IData>,
},
disabled: {
type: Boolean,
},
},
emits: {
change: (_value: Array<string> | string | null, _tag?: string) => true,
},
setup(props, { emit }) {
const ns = useNamespace('mpicker');
const c = props.controller!;
// 当前表单项绑定值key的集合
const curValue: Ref<Array<string>> = ref([]);
// 实体数据集
const items: Ref<IData[]> = ref([]);
// 选中项key-value键值对
const selectItems: Ref<IData[]> = ref([]);
// 下拉是否打开
const open = ref(false);
// 加载中
const loading: Ref<boolean> = ref(false);
// 格式化值,把实体属性名格式化成srfkey和srfmajortext
const formatValue = (value: IData[]) => {
let result: IData[] = [];
if (c.keyName !== 'srfkey' || c.textName !== 'srfmajortext') {
value.forEach((item: IData) => {
result.push({
srfmajortext: item[c.textName] || item.srfmajortext,
srfkey: item[c.keyName],
});
});
} else {
result = value;
}
return result;
};
// 解析值,把srfkey和srfmajortext解析成实体属性名
const parseValue = (value: IData[]) => {
let result: IData[] = [];
if (c.keyName !== 'srfkey' || c.textName !== 'srfmajortext') {
value.forEach((item: IData) => {
result.push({
[c.textName]: item.srfmajortext,
[c.keyName]: item.srfkey,
});
});
} else {
result = value;
}
return result;
};
// 监听传入值
watch(
() => props.value,
newVal => {
curValue.value = [];
selectItems.value = [];
if (newVal) {
try {
selectItems.value = parseValue(JSON.parse(newVal));
selectItems.value.forEach((item: IData) => {
curValue.value.push(item[c.keyName]);
const index = items.value.findIndex(i =>
Object.is(i[c.keyName], item[c.keyName]),
);
if (index < 0) {
items.value.push({
[c.keyName]: item[c.keyName],
[c.textName]: item[c.textName] || item.srfmajortext,
});
}
});
} catch (error) {
if ((error as IData).name === 'SyntaxError') {
const srfkeys: string[] = newVal.split(',');
let srfmajortexts: string[] = [];
if (c.valueItem && props.data![c.valueItem]) {
srfmajortexts = props.data![c.valueItem].split(',');
}
if (
srfkeys &&
srfkeys.length > 0 &&
srfmajortexts &&
srfmajortexts.length > 0 &&
srfkeys.length === srfmajortexts.length
) {
srfkeys.forEach((id: string, index: number) => {
curValue.value.push(id);
selectItems.value.push({
[c.keyName]: id,
[c.textName]: srfmajortexts[index],
});
const _index = items.value.findIndex(i =>
Object.is(i[c.keyName], id),
);
if (_index < 0) {
items.value.push({
[c.keyName]: id,
[c.textName]: srfmajortexts[index],
});
}
});
}
}
}
}
},
{ immediate: true, deep: true },
);
// 处理视图关闭,往外抛值
const handleOpenViewClose = (result: IData[]) => {
const selects: IData[] = [];
if (result && Array.isArray(result)) {
result.forEach((select: IData) => {
selects.push({
[c.keyName]: select[c.keyName] || select.srfkey,
[c.textName]: select[c.textName] || select.srfmajortext,
});
const index = items.value.findIndex(item =>
Object.is(item[c.keyName], select[c.keyName]),
);
if (index < 0) {
items.value.push({
[c.textName]: select[c.textName] || select.srfmajortext,
[c.keyName]: select[c.keyName],
});
}
});
}
if (props.data) {
const value =
selects.length > 0 ? JSON.stringify(formatValue(selects)) : '';
emit('change', value);
}
};
// 打开数据选择视图
const openPickUpView = async () => {
// TODO 后续参数
// const _selectItems: IData[] = JSON.parse(JSON.stringify(selectItems.value));
// if (!Object.is(c.keyName, 'srfkey')) {
// _selectItems.forEach((item: IData, index: number) => {
// _selectItems[index].srfkey = item[c.keyName];
// });
// }
// if (!Object.is(c.textName, 'srfmajortext')) {
// _selectItems.forEach((item: IData, index: number) => {
// _selectItems[index].srfmajortext = item[c.textName] || item.srfmajortext;
// });
// }
// Object.assign(c.context, {
// srfparentdata: { srfparentkey: props.data[c.keyName] },
// });
// Object.assign(c.params, { selectedData: [..._selectItems] });
const res = await c.openPickUpView(props.data!);
if (res) {
handleOpenViewClose(res);
}
};
// 下拉选中回调
const onSelect = (selects: string[]) => {
const val: Array<IData> = [];
if (selects.length > 0) {
selects.forEach((select: string) => {
let index = items.value.findIndex(item =>
Object.is(item[c.keyName], select),
);
if (index >= 0) {
const item = items.value[index];
val.push({
[c.keyName]: item[c.keyName],
[c.textName]: item[c.textName] || item.srfmajortext,
});
} else {
index = selectItems.value.findIndex((item: IData) =>
Object.is(item[c.keyName], select),
);
if (index >= 0) {
const item = selectItems.value[index];
val.push(item);
}
}
});
const value = val.length > 0 ? JSON.stringify(formatValue(val)) : '';
emit('change', value);
} else {
emit('change', '');
}
};
// 搜索
const onSearch = async (query: string) => {
if (c.model.appDataEntity) {
loading.value = true;
try {
const res = await c.getServiceData(query, props.data!);
loading.value = false;
if (res) {
items.value = res.data as IData[];
}
} catch (error) {
loading.value = false;
}
}
};
// 下拉打开
const onSelectOpen = (flag: boolean) => {
open.value = flag;
if (open.value) {
items.value = [];
onSearch('');
}
};
return {
ns,
c,
curValue,
loading,
items,
onSearch,
onSelectOpen,
onSelect,
openPickUpView,
};
},
render() {
return (
<div class={this.ns.b()}>
<i-select
value={this.curValue}
filterable
multiple
loading={this.loading}
placeholder={this.c.placeHolder}
remote-method={this.onSearch}
on-on-open-change={this.onSelectOpen}
on-on-change={this.onSelect}
disabled={this.disabled}
>
{this.items.map((item, index) => {
return (
<i-option key={index} value={item[this.c.keyName]}>
{item[this.c.textName]}
</i-option>
);
})}
</i-select>
<div class={this.ns.e('btns')}>
{this.c.pickupView ? (
<i-button
icon='ios-search'
on-click={this.openPickUpView}
type={'text'}
></i-button>
) : null}
</div>
</div>
);
},
});
import { PickerEditorController } from '@ibiz-template/controller';
import { ref, Ref, watch, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import { debounce } from 'lodash-es';
import { isNil } from 'ramda';
import '@ibiz-template/theme/style/components/editor/ibiz-picker-dropdown/ibiz-picker-dropdown.scss';
export const IBizPickerDropDown = defineComponent({
name: 'IBizPickerDropDown',
props: {
value: {
type: String,
},
controller: {
type: PickerEditorController,
},
data: {
type: Object as PropType<IData>,
required: true,
},
disabled: {
type: Boolean,
},
},
emits: {
change: (_value?: string | Array<string> | IData | null, _tag?: string) =>
true,
},
setup(props, { emit }) {
const ns = useNamespace('picker-dropdown');
const c = props.controller as PickerEditorController;
// 允许搜索
const allowSearch = ref(true);
// 当前值
const curValue: Ref<Array<string> | string | null> = ref('');
// 实体数据集
const items: Ref<IData[]> = ref([]);
// 是否显示所有数据
const isShowAll = ref(true);
watch(
() => props.value,
newVal => {
if (newVal || newVal === null) {
curValue.value = newVal;
const value = props.data[c.valueItem];
const index = items.value.findIndex((item: IData) =>
Object.is(item[c.keyName], value),
);
if (index !== -1) {
return;
}
// items里匹配不到当前值项值时,生成自身的选项
items.value = [];
if (!isNil(newVal) && !isNil(value)) {
items.value.push({ [c.textName]: newVal, [c.keyName]: value });
}
}
},
{ immediate: true },
);
// 获取关联数据项值
const refValue = ref('');
watch(
() => props.data[c.valueItem],
(newVal, oldVal) => {
if (newVal !== oldVal) {
refValue.value = newVal;
}
},
{ immediate: true, deep: true },
);
// 下拉是否打开
const open = ref(false);
// 加载中
const loading: Ref<boolean> = ref(false);
// 往外抛值
const onACSelect = async (item: IData) => {
// 处理回填数据
const dataItems = await c.calcFillDataItems(item);
if (dataItems.length) {
dataItems.forEach(dataItem => {
emit('change', dataItem.value, dataItem.name);
});
}
// 处理值项和本身的值
if (c.valueItem) {
emit('change', item[c.keyName], c.valueItem);
}
emit('change', item[c.textName]);
isShowAll.value = true;
};
// 值变更
const onSelect = (select: string | Array<string>) => {
const index = items.value.findIndex(item =>
Object.is(item[c.keyName], select),
);
if (index >= 0) {
onACSelect(items.value[index]);
}
};
// 在搜索中时,再次触发搜索记录搜索值,等待上次搜索触发完成后再次搜索
let waitQuery: string | null = null;
// 搜索
const onSearch = async (query: string) => {
if (c.model.appDataEntity && loading.value === false) {
loading.value = true;
try {
const res = await c.getServiceData(query, props.data!);
if (res) {
items.value = res.data as IData[];
}
} finally {
loading.value = false;
if (waitQuery != null) {
const selfQuery = waitQuery;
waitQuery = null;
await onSearch(selfQuery);
}
}
} else {
waitQuery = query;
}
};
// 搜索词改变时触发
const queryChange = debounce((query: string) => {
if (
(query !== curValue.value || curValue.value === null) &&
allowSearch.value
) {
isShowAll.value = false;
onSearch(query);
}
}, 1000);
// 下拉打开
const onSelectOpen = (flag: boolean) => {
open.value = flag;
if (open.value) {
allowSearch.value = true;
items.value = [];
const query = isShowAll.value ? '' : curValue.value;
onSearch(query as string);
}
};
// 清除
const onClear = () => {
// 清空回填数据
const dataItems = c.model.deACMode?.dataItems;
if (dataItems?.length) {
dataItems.forEach(dataItem => {
emit('change', null, dataItem.name);
});
}
if (c.valueItem) {
emit('change', null, c.valueItem);
}
emit('change', null);
};
return {
ns,
c,
refValue,
loading,
items,
isShowAll,
allowSearch,
onSearch,
queryChange,
onSelectOpen,
onClear,
onSelect,
};
},
render() {
return (
<div class={this.ns.b()}>
<i-select
value={this.refValue}
filterable
allow-clear
clearable
remote-method={this.queryChange}
loading={this.loading}
placeholder={this.c.placeHolder}
on-on-open-change={this.onSelectOpen}
on-on-change={this.onSelect}
on-on-clear={this.onClear}
disabled={this.disabled}
>
{this.items.map((item, index) => {
return (
<i-option value={item[this.c.keyName]} key={index}>
{item[this.c.textName]}
</i-option>
);
})}
</i-select>
</div>
);
},
});
import { PickerEditorController } from '@ibiz-template/controller';
import { defineComponent, PropType, ref, Ref, watch } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
export const IBizPickerLink = defineComponent({
name: 'IBizPickerLink',
props: {
value: {
type: String,
},
controller: {
type: PickerEditorController,
},
data: {
type: Object as PropType<IData>,
},
},
emits: {
change: (_value: Array<string | number> | string, _tag?: string) => true,
},
setup(props, { emit }) {
const ns = useNamespace('picker-link');
const c = props.controller!;
const curValue: Ref<string> = ref('');
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal && newVal !== oldVal) {
curValue.value = `${newVal}`;
}
},
{ immediate: true },
);
// 处理视图关闭,往外抛值
const handleOpenViewClose = (result: IData[]) => {
const item: IData = {};
if (result && Array.isArray(result)) {
Object.assign(item, result[0]);
}
if (c.valueItem) {
emit('change', item[c.keyName], c.valueItem);
}
emit('change', item[c.textName]);
};
// 打开数据链接视图
const openLinkView = async () => {
const res = await c.openLinkView(props.data!);
if (res) {
handleOpenViewClose(res);
}
};
return { ns, openLinkView, curValue };
},
render() {
return (
<div class={this.ns.b()}>
<a on-click={this.openLinkView}>{this.curValue}</a>
</div>
);
},
});
import { PickerEditorController } from '@ibiz-template/controller';
import { ref, watch, Ref, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import { isEmpty } from 'ramda';
import '@ibiz-template/theme/style/components/editor/ibiz-picker/ibiz-picker.scss';
export const IBizPicker = defineComponent({
name: 'IBizPicker',
props: {
value: {
type: String,
},
controller: {
type: PickerEditorController,
},
data: {
type: Object as PropType<IData>,
required: true,
},
disabled: {
type: Boolean,
},
},
setup(props, { emit }) {
const ns = useNamespace('picker');
const c = props.controller!;
// 当前值
const curValue: Ref<Array<string> | string | null> = ref('');
// 实体数据集
const items: Ref<IData[]> = ref([]);
// 是否显示所有数据
const isShowAll = ref(true);
watch(
() => props.value,
newVal => {
if (newVal || newVal === null) {
curValue.value = newVal;
if (newVal === null) {
curValue.value = '';
}
const value = props.data[c.valueItem];
const index = items.value.findIndex((item: IData) =>
Object.is(item[c.keyName], value),
);
if (index !== -1) {
return;
}
// items里匹配不到当前值项值时,生成自身的选项
items.value = [];
if (!isEmpty(newVal) && !isEmpty(value)) {
items.value.push({ [c.textName]: newVal, [c.keyName]: value });
}
}
},
{ immediate: true },
);
// 处理选中数据后的处理逻辑
const handleDataSelect = async (data: IData) => {
// 处理回填数据
const dataItems = await c.calcFillDataItems(data);
if (dataItems.length) {
dataItems.forEach(dataItem => {
emit('change', dataItem.value, dataItem.name);
});
}
// 处理值项和本身的值
if (c.valueItem) {
emit('change', data[c.keyName], c.valueItem);
}
emit('change', data[c.textName]);
};
// 打开数据选择视图
const openPickUpView = async () => {
const res = await c.openPickUpView(props.data);
if (res && res[0]) {
await handleDataSelect(res[0]);
}
};
// 打开数据链接视图
const openLinkView = async () => {
await c.openLinkView(props.data);
};
// 往外抛值
const onACSelect = async (item: IData) => {
await handleDataSelect(item);
isShowAll.value = true;
};
// 搜索
const onSearch = async (query: string) => {
if (c.model.appDataEntity) {
const res = await c.getServiceData(query, props.data);
if (res) {
items.value = res.data as IData[];
}
}
};
// 清除
const onClear = () => {
// 清空回填数据
const dataItems = c.model.deACMode?.dataItems;
if (dataItems?.length) {
dataItems.forEach(dataItem => {
emit('change', null, dataItem.name);
});
}
if (c.valueItem) {
emit('change', null, c.valueItem);
}
emit('change', null);
};
// 聚焦
const onFocus = (e: IData) => {
const query = isShowAll.value ? '' : e.target.value;
onSearch(query);
};
const closeCircle = (c.linkView ? 1 : 0) + (c.pickupView ? 1 : 0);
return {
ns,
c,
curValue,
items,
openPickUpView,
openLinkView,
onACSelect,
onSearch,
onClear,
onFocus,
closeCircle,
};
},
render() {
return (
<div class={this.ns.b()}>
{this.c.noAC ? (
<i-input
value={this.curValue}
clearable
placeholder={this.c.placeHolder}
on-on-clear={this.onClear}
disabled={this.disabled}
>
{this.$slots.append}
{!this.$slots.append && this.c.pickupView ? (
<i-button
key='ios-search'
icon='ios-search'
on-click={this.openPickUpView}
></i-button>
) : null}
{!this.$slots.append && this.c.linkView ? (
<i-button
key='ios-search-outline'
icon='ios-search-outline'
on-click='openLinkView'
></i-button>
) : null}
</i-input>
) : (
<div
class={[
this.ns.e('autocomplete'),
this.ns.m(this.closeCircle.toString()),
]}
>
<i-auto-complete
value={this.curValue}
placeholder={this.c.placeHolder}
placement='bottom'
clearable
transfer-class-name={this.ns.e('transfer')}
on-on-focus={this.onFocus}
on-on-search={this.onSearch}
on-on-clear={this.onClear}
disabled={this.disabled}
>
{this.items.map(item => {
return (
<div
class={this.ns.e('transfer-item')}
on-click={() => {
this.onACSelect(item);
}}
>
{item[this.c.textName]}
</div>
);
})}
</i-auto-complete>
<div class={this.ns.e('btns')}>
{this.c.pickupView ? (
<i-button
icon='ios-search'
on-click={this.openPickUpView}
type={'text'}
></i-button>
) : null}
{this.c.linkView ? (
<i-button
on-click={this.openLinkView}
type={'text'}
class={this.ns.e('link')}
>
<ion-icon src={'./assets/img/link.svg'}></ion-icon>
</i-button>
) : null}
</div>
</div>
)}
</div>
);
},
});
export { IBizPicker } from './ibiz-picker/ibiz-picker';
export { IBizPickerDropDown } from './ibiz-picker-dropdown/ibiz-picker-dropdown';
export { IBizMPicker } from './ibiz-mpicker/ibiz-mpicker';
export { IBizPickerLink } from './ibiz-picker-link/ibiz-picker-link';
import { DatePickerEditorController } from '@ibiz-template/controller';
import { ref, watch, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-date-picker/ibiz-date-picker.scss';
export const IBizDatePicker = defineComponent({
name: 'IBizDatePicker',
props: {
value: {
type: String,
},
controller: {
type: DatePickerEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
},
disabled: {
type: Boolean,
},
},
emits: {
change: (_value: string) => true,
},
setup(props, { emit }) {
const ns = useNamespace('date-picker');
const c = props.controller;
const editorModel = c!.model;
const type = ref('date');
const format = ref('yyyy-MM-dd');
switch (editorModel.editorType) {
// 时间选择器
case 'DATEPICKER':
type.value = 'datetime';
format.value = 'yyyy-MM-dd HH:mm:ss';
break;
// 时间选择控件
case 'DATEPICKEREX':
type.value = 'date';
format.value = 'yyyy-MM-dd HH:mm:ss';
break;
// 时间选择控件_无小时
case 'DATEPICKEREX_NOTIME':
type.value = 'date';
format.value = 'yyyy-MM-dd';
break;
// 时间选择控件_小时
case 'DATEPICKEREX_HOUR':
type.value = 'datetime';
format.value = 'yyyy-MM-dd HH';
break;
// 时间选择控件_分钟
case 'DATEPICKEREX_MINUTE':
type.value = 'datetime';
format.value = 'yyyy-MM-dd HH:mm';
break;
// 时间选择控件_秒钟
case 'DATEPICKEREX_SECOND':
type.value = 'datetime';
format.value = 'yyyy-MM-dd HH:mm:ss';
break;
// 时间选择控件_无日期
case 'DATEPICKEREX_NODAY':
type.value = 'datetime';
format.value = 'HH:mm:ss';
break;
// 时间选择控件_无日期无秒钟
case 'DATEPICKEREX_NODAY_NOSECOND':
type.value = 'datetime';
format.value = 'HH:mm';
break;
// 时间选择控件_无秒钟
case 'DATEPICKEREX_NOSECOND':
type.value = 'datetime';
format.value = 'yyyy-MM-dd HH:mm';
break;
default:
break;
}
// 值格式化
const valueFormat = c!.valueFormat;
if (valueFormat) {
const tempFormat: string = valueFormat
.replace('YYYY', 'yyyy')
.replace('DD', 'dd');
format.value = tempFormat;
}
const currentVal = ref();
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
currentVal.value = newVal || null;
}
},
{ immediate: true },
);
// 处理值变更
const handleChange = (date: string, _dateType: IData) => {
emit('change', date);
};
return { ns, c, editorModel, type, format, currentVal, handleChange };
},
render() {
return (
<div class={this.ns.b()}>
<i-CalendarPicker
type={this.type}
format={this.format}
placeholder={this.c!.placeHolder}
value={this.currentVal}
readonly={this.c!.model.readOnly}
on-on-change={this.handleChange}
disabled={this.disabled}
></i-CalendarPicker>
</div>
);
},
});
export { IBizDatePicker } from './ibiz-date-picker/ibiz-date-picker';
import { DropDownListEditorController } from '@ibiz-template/controller';
import { ref, Ref, defineComponent, PropType, computed } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-dropdown-list/ibiz-dropdown-list.scss';
export const IBizDropDownList = defineComponent({
name: 'IBizDropDownList',
props: {
value: {
type: String,
},
controller: {
type: DropDownListEditorController,
},
data: {
type: Object as PropType<IData>,
},
disabled: {
type: Boolean,
},
},
emits: {
change: (_value: string | Array<string> | null) => true,
},
setup(props, { emit }) {
const ns = useNamespace('dropdown-list');
const c = props.controller;
// 是否是树形
const hasChildren = ref(false);
// 代码表
const items: Ref<readonly IData[]> = ref([]);
c!.loadCodeList(props.data!).then((codeList: readonly IData[]) => {
items.value = codeList;
for (let i = 0; i < codeList.length; i++) {
const _item = codeList[i];
if (_item.children) {
hasChildren.value = true;
break;
}
}
});
// 当前值
const curValue: Ref<Array<string> | string | undefined> = computed({
get() {
if (props.value) {
return c!.multiple ? props.value?.split(',') : props.value;
}
return '';
},
set(select: string | Array<string>) {
const value =
Object.prototype.toString.call(select) === '[object Array]'
? (select as Array<string>).join(',')
: select;
if (value) {
emit('change', value);
} else {
emit('change', null);
}
},
});
return { ns, c, curValue, items, hasChildren };
},
render() {
return (
<div class={this.ns.b()}>
{this.hasChildren ? (
<div class={this.ns.e('tree-select')}>
<app-select-tree
value={this.curValue}
nodes-data={this.items}
multiple={this.c!.multiple}
></app-select-tree>
</div>
) : (
<div class={this.ns.e('select')}>
<i-select
v-model={this.curValue}
allow-clear
clearable
class={this.ns.e('dropdown-list')}
multiple={this.c!.multiple}
placeholder={this.c!.placeHolder}
disabled={this.disabled}
>
{this.items.map(item => {
return <i-option value={item.value}>{item.text}</i-option>;
})}
</i-select>
</div>
)}
</div>
);
},
});
export { IBizDropDownList } from './ibiz-dropdown-list/ibiz-dropdown-list';
export * from './span';
export * from './text-box';
export * from './check-box-list';
export * from './radio-button-list';
export * from './date-picker';
export * from './dropdown-list';
export * from './data-picker';
export * from './upload';
export { NotSupportedEditor } from './not-supported-editor/not-supported-editor';
......@@ -3,7 +3,8 @@ import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent } from 'vue';
import '@ibiz-template/theme/style/components/editor/not-supported-editor/not-supported-editor.scss';
export default defineComponent({
export const NotSupportedEditor = defineComponent({
name: 'NotSupportedEditor',
props: {
modelData: {
type: EditorModel,
......
import { RadioButtonListEditorController } from '@ibiz-template/controller';
import { computed, defineComponent, ref } from 'vue';
import type { PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-radio/ibiz-radio.scss';
export const IBizRadio = defineComponent({
name: 'IBizRadio',
props: {
value: [String, Number],
controller: {
type: RadioButtonListEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
emits: {
change: (_value: string | number) => true,
},
setup(props, { emit }) {
const ns = useNamespace('radio');
const c = props.controller;
const editorModel = c.model;
const selectValue = computed({
get() {
return props.value || '';
},
set(newValue: string | number) {
emit('change', newValue);
},
});
const onSelectValueChange = (value: string | number) => {
selectValue.value = value;
};
// 代码表
const items = ref<readonly IData[]>([]);
props.controller.loadCodeList(props.data).then(codeList => {
items.value = codeList;
});
return {
ns,
editorModel,
selectValue,
items,
onSelectValueChange,
};
},
render() {
return (
<div class={this.ns.b()}>
<i-radio-group
class={this.ns.b('group')}
value={this.selectValue}
on-on-change={this.onSelectValueChange}
>
{this.items.map((_item, index: number) => (
<i-radio
key={index}
label={_item.value}
readonly={this.editorModel.readOnly}
>
<span class={this.ns.be('group', 'text')}>{_item.text}</span>
</i-radio>
))}
</i-radio-group>
</div>
);
},
});
export { IBizRadio } from './ibiz-radio/ibiz-radio';
import { ref, defineComponent, Ref, watch } from 'vue';
import type { PropType } from 'vue';
import { SpanEditorController } from '@ibiz-template/controller';
import { useNamespace } from '@ibiz-template/vue-util';
import dayjs from 'dayjs';
import '@ibiz-template/theme/style/components/editor/ibiz-span/ibiz-span.scss';
export const IBizSpan = defineComponent({
name: 'IBizSpan',
props: {
value: String,
controller: {
type: SpanEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
setup(props) {
const ns = useNamespace('span');
const c = props.controller;
const text: Ref<string> = ref('');
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
if (!newVal) {
text.value = '';
return;
}
if (c.valueFormat) {
text.value = dayjs(newVal).format(c.valueFormat);
} else {
text.value = newVal || '';
}
if (c.unitName) {
text.value += c.unitName;
}
}
},
{
immediate: true,
},
);
return {
ns,
text,
};
},
render() {
return <span class={this.ns.b()}>{this.text}</span>;
},
});
export { IBizSpan } from './ibiz-span/ibiz-span';
import { defineComponent } from 'vue';
import {
getGridInputProps,
useGridCellEditor,
useNamespace,
} from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-input/ibiz-input.scss';
export const IBizGridInput = defineComponent({
name: 'IBizGridInput',
props: getGridInputProps(),
setup(props) {
const ns = useNamespace('grid-input');
const { isInfoMode, componentRef, onOperateChange, onChange } =
useGridCellEditor(props.rowDataChange, {
isCacheValue: true,
});
return {
ns,
isInfoMode,
componentRef,
onOperateChange,
onChange,
};
},
render(h) {
return (
<div
ref='componentRef'
class={`${this.ns.b()} ibiz-grid-editor`}
onDblclick={evt => evt.stopPropagation()}
onClick={evt => evt.stopPropagation()}
>
{h('IBizInput', {
props: {
...this.$props,
infoMode: this.isInfoMode,
},
on: {
change: this.onChange,
operate: this.onOperateChange,
},
})}
</div>
);
},
});
import { defineComponent, ref, watch } from 'vue';
import type { PropType } from 'vue';
import { TextBoxEditorController } from '@ibiz-template/controller';
import { useNamespace } from '@ibiz-template/vue-util';
export const IBizInputNumber = defineComponent({
name: 'IBizInputNumber',
props: {
value: Number,
controller: {
type: TextBoxEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
emits: {
change: (_value: number | null) => true,
},
setup(props, { emit }) {
const ns = useNamespace('input-number');
const c = props.controller;
const currentVal = ref<number | null>(null);
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
currentVal.value = newVal!;
}
},
{ immediate: true },
);
const handleChange = (e: number | null) => {
emit('change', e);
};
return {
ns,
c,
currentVal,
handleChange,
};
},
render() {
return (
<div class={this.ns.b()}>
<i-input-number
value={this.currentVal}
placeholder={this.c.placeHolder}
readonly={this.c.model.readOnly}
precision={this.c.model.precision}
on-on-change={this.handleChange}
></i-input-number>
</div>
);
},
});
import { computed, defineComponent, ref, watch } from 'vue';
import { debounce } from 'lodash-es';
import {
getEditorEmits,
getInputProps,
useNamespace,
} from '@ibiz-template/vue-util';
import '@ibiz-template/theme/style/components/editor/ibiz-input/ibiz-input.scss';
export const IBizInput = defineComponent({
name: 'IBizInput',
props: getInputProps(),
emits: getEditorEmits(),
setup(props, { emit }) {
const ns = useNamespace('input');
const c = props.controller;
const editorModel = c.model;
// 文本域默认行数,仅在 textarea 类型下有效
const rows = ref(2);
if (editorModel.editorType === 'TEXTAREA_10') {
rows.value = 10;
}
// 类型
const type = computed(() => {
switch (editorModel.editorType) {
case 'TEXTBOX':
return 'text';
case 'PASSWORD':
return 'password';
case 'TEXTAREA':
case 'TEXTAREA_10':
return 'textarea';
default:
return 'string';
}
});
const currentVal = ref<string | number>('');
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
if (newVal === null) {
currentVal.value = '';
} else {
currentVal.value = newVal as string | number;
}
}
},
{ immediate: true },
);
// 值变更
const handleChange = debounce((e: IData) => {
emit('change', e.target.value);
}, 500);
return {
ns,
rows,
type,
currentVal,
handleChange,
};
},
render() {
return (
<div
class={`${this.ns.b()} ${this.ns.is(
'textarea',
Object.is(this.type, 'textarea'),
)}`}
>
{this.infoMode ? (
this.currentVal
) : (
<i-input
value={this.currentVal}
placeholder={this.controller.placeHolder}
readonly={this.controller.model.readOnly}
type={this.type}
rows={this.rows}
on-on-change={this.handleChange}
class={this.ns.b('input')}
disabled={this.disabled}
></i-input>
)}
</div>
);
},
});
export { IBizInput } from './ibiz-input/ibiz-input';
export { IBizGridInput } from './ibiz-grid-input/ibiz-grid-input';
export { IBizInputNumber } from './ibiz-input-number/ibiz-input-number';
import { UploadEditorController } from '@ibiz-template/controller';
import { getCookie } from 'qx-util';
import { onMounted, ref, Ref, watch, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import { RuntimeError } from '@ibiz-template/core';
import '@ibiz-template/theme/style/components/editor/ibiz-file-upload/ibiz-file-upload.scss';
export const IBizFileUpload = defineComponent({
name: 'IBizFileUpload',
props: {
value: {
type: String,
},
controller: {
type: UploadEditorController,
require: true,
},
data: {
type: Object as PropType<IData>,
},
},
emits: {
change: (_value: string | null) => true,
},
setup(props, { emit }) {
const ns = useNamespace('file-upload');
const c = props.controller as UploadEditorController;
// 文件列表
const files: Ref<IData[]> = ref([]);
// 请求头
const headers: Ref<IData> = ref({});
// 上传文件路径
const uploadUrl: Ref<string> = ref(ibiz.env.baseUrl + ibiz.env.UploadFile);
// 下载文件路径
const downloadUrl: Ref<string> = ref(ibiz.env.ExportFile);
// 设置files
const setFiles = (value: string | null | undefined) => {
if (value === null) {
files.value = [];
}
if (!value) {
return;
}
const _files = JSON.parse(value);
if (
value &&
Object.prototype.toString.call(_files) === '[object Array]'
) {
files.value = _files;
} else {
files.value = [];
}
};
// 文件上传缓存对象
const uploadCache: IData = {
count: 0,
cacheFiles: [],
};
// 处理参数
const dataProcess = () => {
let _url = ibiz.env.baseUrl + ibiz.env.UploadFile;
if (c.upload_params.length > 0) {
_url += '?';
c.upload_params.forEach((item: IData, i: number) => {
_url += `${Object.keys(item)[0]}=${Object.values(item)[0]}`;
if (i < c.upload_params.length - 1) {
_url += '&';
}
});
}
uploadUrl.value = _url;
files.value.forEach((file: IData) => {
let url = `${downloadUrl.value}/${file.id}`;
if (c.export_params.length > 0) {
url += '?';
c.export_params.forEach((item: IData, i: number) => {
url += `${Object.keys(item)[0]}=${Object.values(item)[0]}`;
if (i < c.export_params.length - 1) {
url += '&';
}
});
}
// eslint-disable-next-line no-param-reassign
file.url = url;
});
};
// 设置头
const setHeaders = () => {
if (getCookie('access_token')) {
headers.value.Authorization = `Bearer ${getCookie('access_token')}`;
}
};
setHeaders();
watch(
() => props.value,
newVal => {
setFiles(newVal);
dataProcess();
},
{ immediate: true },
);
watch(
() => props.data,
newVal => {
c.getParams(newVal!);
},
{ immediate: true, deep: true },
);
onMounted(() => {
c.getParams(props.data!);
setFiles(props.value);
dataProcess();
});
// 上传前回调
const beforeUpload = () => {
uploadCache.count += 1;
};
// 上传成功回调
const onSuccess = (response: IData) => {
if (!response) {
return;
}
// 处理回调数据,并缓存
const arr: IData[] = [];
if (response?.length > 0) {
for (let index = 0; index < response.length; index++) {
const file = response[index];
arr.push({ name: file.filename, id: file.fileid });
}
} else {
arr.push({ name: response.filename, id: response.fileid });
}
uploadCache.cacheFiles.push(arr);
uploadCache.count -= 1;
// 回调都结束后的处理
if (uploadCache.count === 0) {
const result: IData[] = [];
// 添加已有的文件数据
files.value.forEach((_file: IData) => {
result.push({ name: _file.name, id: _file.id });
});
// 添加缓存的文件数据
uploadCache.cacheFiles.forEach((item: IData[]) => {
result.push(...item);
});
// 抛出值变更事件
const value: string | null =
result.length > 0 ? JSON.stringify(result) : null;
emit('change', value);
// 清空缓存的文件数据
uploadCache.cacheFiles = [];
}
};
// 上传失败回调
const onError = (error: IData) => {
uploadCache.count -= 1;
throw error;
};
// 删除回调
const onRemove = (file: IData, fileList: IData[]) => {
const arr: Array<IData> = [];
fileList.forEach((f: IData) => {
if (f.id !== file.id) {
arr.push({ name: f.name, id: f.id });
}
});
const value: string | null = arr.length > 0 ? JSON.stringify(arr) : null;
emit('change', value);
};
// 计算文件mime类型
const calcFilemime = (filetype: string) => {
let mime = 'image/png';
switch (filetype) {
case '.wps':
mime = 'application/kswps';
break;
case '.doc':
mime = 'application/msword';
break;
case '.docx':
mime =
'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
break;
case '.txt':
mime = 'text/plain';
break;
case '.zip':
mime = 'application/zip';
break;
case '.png':
mime = 'image/png';
break;
case '.gif':
mime = 'image/gif';
break;
case '.jpeg':
mime = 'image/jpeg';
break;
case '.jpg':
mime = 'image/jpeg';
break;
case '.rtf':
mime = 'application/rtf';
break;
case '.avi':
mime = 'video/x-msvideo';
break;
case '.gz':
mime = 'application/x-gzip';
break;
case '.tar':
mime = 'application/x-tar';
break;
default:
mime = 'image/png';
}
return mime;
};
/**
* 下载文件
*/
const DownloadFile = (url: string, file: IData) => {
// 发送get请求
ibiz.net
.get(
url,
{},
{},
{
responseType: 'blob',
},
)
.then((response: IData) => {
if (!response || response.status !== 200) {
throw new RuntimeError('下载文件失败');
}
// 请求成功,后台返回的是一个文件流
if (response.data) {
// 获取文件名
const filename = file.name;
const ext = `.${filename.split('.').pop()}`;
const filetype = calcFilemime(ext);
// 用blob对象获取文件流
const blob = new Blob([response.data], { type: filetype });
// 通过文件流创建下载链接
const href = URL.createObjectURL(blob);
// 创建一个a元素并设置相关属性
const a = document.createElement('a');
a.href = href;
a.download = filename;
// 添加a元素到当前网页
document.body.appendChild(a);
// 触发a元素的点击事件,实现下载
a.click();
// 从当前网页移除a元素
document.body.removeChild(a);
// 释放blob对象
URL.revokeObjectURL(href);
} else {
throw new RuntimeError('文件流数据不存在');
}
});
};
// 下载文件
const onDownload = (file: IData) => {
const url = `${downloadUrl.value}/${file.id}`;
DownloadFile(url, file);
};
return {
ns,
c,
uploadUrl,
headers,
files,
onDownload,
onError,
onRemove,
onSuccess,
beforeUpload,
};
},
render(h) {
return (
<div class={this.ns.b()}>
{h(
'IUpload',
{
ref: 'fileUpload',
props: {
action: this.uploadUrl,
headers: this.headers,
'default-file-list': this.files,
multiple: this.c.multiple,
type: this.c.isDrag ? 'drag' : 'select',
accept: this.c.accept,
'before-upload': this.beforeUpload,
'on-success': this.onSuccess,
'on-error': this.onError,
'on-remove': this.onRemove,
'on-preview': this.onDownload,
},
},
[
<i-button
icon='ios-cloud-upload-outline'
class={this.ns.b('button')}
>
上传文件
</i-button>,
],
)}
</div>
);
},
});
export { IBizFileUpload } from './ibiz-file-upload/ibiz-file-upload';
export { IBizGridFileUpload } from './ibiz-grid-file-upload/ibiz-grid-file-upload';
export { IBizImageUpload } from './ibiz-image-upload/ibiz-image-upload';
......@@ -40,7 +40,7 @@ export const FormItem = defineComponent({
value: this.value,
data,
controller: this.controller.editor,
disable: this.controller.disabled,
disabled: this.controller.disabled,
},
on: {
change: this.onValueChange,
......
......@@ -21,18 +21,14 @@ export const GridColumn = defineComponent({
},
},
setup(props) {
/** 先看列是否启用行编辑,然后是否有配置编辑项,最后才看rowController控的状态 */
const showRowEdit = computed(() => {
return (
props.fieldColumn.enableRowEdit &&
props.editItem &&
props.row.showRowEdit
);
/** 先看列是否启用行编辑,然后是否有配置编辑项 */
const renderRowEdit = computed(() => {
return props.fieldColumn.enableRowEdit && props.editItem;
});
return { showRowEdit };
return { renderRowEdit };
},
render() {
if (this.showRowEdit && this.editItem) {
if (this.renderRowEdit) {
return (
<grid-edit-item
controller={this.editItem}
......
......@@ -3,6 +3,8 @@ import {
GridRowController,
} from '@ibiz-template/controller';
import { defineComponent } from 'vue';
import '@ibiz-template/theme/style/components/widgets/grid/grid-edit-item.scss';
import { useNamespace } from '@ibiz-template/vue-util';
export const GridEditItem = defineComponent({
name: 'GridEditItem',
......@@ -17,16 +19,24 @@ export const GridEditItem = defineComponent({
},
},
setup(props) {
const ns = useNamespace('grid-edit-item');
// 编辑器值变更事件
const onValueChange = (val: unknown, name?: string) => {
props.controller.setRowValue(props.row, val, name);
const rowDataChange = async (val: unknown, name?: string) => {
const _name = name || props.controller.model.codeName;
// 值不变则无后续,也不保存
if (props.row.data[_name] === val) {
return;
}
await props.controller.setRowValue(props.row, val, name);
// 保存时机由界面控制
await props.controller.grid.save(props.row.data);
};
const onStopPropagation = (e: MouseEvent) => {
e.stopPropagation();
};
return { onValueChange, onStopPropagation };
return { ns, rowDataChange, onStopPropagation };
},
render(h) {
const codeName = this.controller.model.codeName;
......@@ -38,17 +48,13 @@ export const GridEditItem = defineComponent({
nativeonClick={this.onStopPropagation}
>
{this.controller.editorProvider &&
h(this.controller.editorProvider.rowEditor, {
h(this.controller.editorProvider.gridEditor, {
props: {
value: this.row.data[codeName],
data: this.row.data,
controller: this.controller.editor,
disable: this.row.columnState[codeName]!.disabled,
},
on: {
change: (val: unknown, name?: string) => {
this.onValueChange(val, name);
},
disabled: this.row.columnState[codeName]!.disabled,
rowDataChange: this.rowDataChange.bind(this),
},
})}
</appGridEditItem>
......
......@@ -50,12 +50,12 @@ export const GridFieldColumn = defineComponent({
const c = this.controller;
const value = this.row.data[c.model.codeName];
return (
<span
<div
class={[this.ns.b(), c.isLinkColumn && this.ns.m('link-column')]}
onClick={this.onCellClick}
>
{this.codeListText || value}
</span>
<span class={this.ns.e('text')}>{this.codeListText || value}</span>
</div>
);
},
});
......
......@@ -5,6 +5,7 @@ import { PiniaVuePlugin } from 'pinia';
import router from './router';
import App from './App';
import { AppRegister } from './app-register';
import UserRegister from './user-register';
import {
MessageUtil,
OpenViewUtil,
......@@ -59,6 +60,7 @@ async function createApp(): Promise<void> {
ibiz.notification = new NotificationUtil();
ibiz.loading = new LoadingUtil();
ibiz.plugin = new PluginFactory();
Vue.use(UserRegister);
},
router,
pinia: piniaInstance,
......
export { PluginFactory } from './plugin-factory/plugin-factory';
export { RemotePluginItem } from './remote-plugin-item/remote-plugin-item';
import Vue from 'vue';
import { RuntimeError } from '@ibiz-template/core';
import { DefectModelError } from '@ibiz-template/model';
import { IPluginFactory } from '@ibiz-template/runtime';
import { IPSAppPFPluginRef, IPSSysPFPlugin } from '@ibiz/dynamic-model-api';
import {
IPluginFactory,
RemotePluginConfig,
RemotePluginItem,
} from '../remote-plugin-item/remote-plugin-item';
} from '@ibiz-template/runtime';
import { IPSAppPFPluginRef, IPSSysPFPlugin } from '@ibiz/dynamic-model-api';
import { basename, dirname, join } from 'path-browserify';
/**
* 插件工具类
......@@ -27,6 +28,15 @@ export class PluginFactory implements IPluginFactory {
*/
protected cache: Map<string, boolean> = new Map();
/**
* 本地开发测试包,只在本地开发生效
*
* @author chitanda
* @date 2022-11-02 21:11:41
* @protected
*/
protected devPackages: Map<string, () => Promise<unknown>> = new Map();
/**
* 插件缓存
*
......@@ -37,6 +47,18 @@ export class PluginFactory implements IPluginFactory {
*/
protected pluginCache: Map<string, RemotePluginItem> = new Map();
/**
* 设置开发插件,用于本地调试
*
* @author chitanda
* @date 2022-11-02 21:11:56
* @param {string} name
* @param {() => Promise<unknown>} fn
*/
setDevPlugin(name: string, fn: () => Promise<unknown>): void {
this.devPackages.set(name, fn);
}
/**
* 加载插件
*
......@@ -64,12 +86,6 @@ export class PluginFactory implements IPluginFactory {
* @return {*} {Promise<boolean>}
*/
async loadPluginRef(pluginRef: IPSAppPFPluginRef): Promise<boolean> {
if (ibiz.env.dev) {
ibiz.log.warn(
`开发模式,远程插件[${pluginRef.name}]不加载。开发测试请在本地引入!`,
);
return true;
}
if (this.pluginCache.has(pluginRef.rTObjectName)) {
return true;
}
......@@ -87,14 +103,13 @@ export class PluginFactory implements IPluginFactory {
config as unknown as RemotePluginConfig,
);
if (remotePlugin) {
await this.loadScript(remotePlugin.script);
if (remotePlugin.styles instanceof Array) {
await this.loadStyles(remotePlugin.styles);
} else {
await this.loadStyles([remotePlugin.styles]);
try {
await this.loadScript(remotePlugin);
this.pluginCache.set(pluginRef.rTObjectName, remotePlugin);
return true;
} catch (error) {
ibiz.log.error(error);
}
this.pluginCache.set(pluginRef.rTObjectName, remotePlugin);
return true;
}
return false;
}
......@@ -103,29 +118,48 @@ export class PluginFactory implements IPluginFactory {
* 加载插件
*
* @author chitanda
* @date 2022-10-31 14:10:10
* @date 2022-11-02 14:11:31
* @protected
* @param {string} scriptUrl
* @param {RemotePluginItem} remotePlugin
* @return {*} {Promise<void>}
*/
protected async loadScript(scriptUrl: string): Promise<void> {
protected async loadScript(remotePlugin: RemotePluginItem): Promise<void> {
const { baseUrl, name, version, system, module } = remotePlugin.config;
const scriptUrl = this.devPackages.has(name)
? join(baseUrl, `${name}@${version}`, module)
: join(baseUrl, `${name}@${version}`, system);
if (scriptUrl) {
if (this.cache.has(scriptUrl)) {
return;
}
let data: IParams | null = null;
const url = this.parseUrl(scriptUrl);
const module = await System.import(url);
if (module) {
if (module.default) {
Vue.use(module.default);
} else {
throw new RuntimeError(
`远程插件加载失败, 远程插件未找到[default]默认导出`,
);
}
if (this.devPackages.has(name)) {
const fn = this.devPackages.get(name)!;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pluginModule = (await fn()) as any;
Vue.use(pluginModule.default, remotePlugin);
this.cache.set(scriptUrl, true);
const mateUrl: string = pluginModule.default.mateUrl || '';
await this.loadStyles(
remotePlugin,
mateUrl.split('/').splice(3).join('/'),
);
} else {
throw new RuntimeError(`远程插件加载失败, 未找到文件`);
data = await System.import(url);
if (data) {
if (data.default) {
Vue.use(data.default, remotePlugin);
} else {
throw new RuntimeError(
`远程插件加载失败, 远程插件未找到[default]默认导出`,
);
}
this.cache.set(scriptUrl, true);
await this.loadStyles(remotePlugin, url);
} else {
throw new RuntimeError(`远程插件加载失败, 未找到文件`);
}
}
}
}
......@@ -134,29 +168,38 @@ export class PluginFactory implements IPluginFactory {
* 加载插件样式文件
*
* @author chitanda
* @date 2022-10-31 12:10:23
* @date 2022-11-02 21:11:51
* @protected
* @param {(string[])} styles
* @param {RemotePluginItem} remotePlugin
* @param {string} [rootJsPath] 入口 js 文件全路径,用于计算拼接 css 文件所在路径
* @return {*} {Promise<void>}
*/
protected async loadStyles(styles: string[]): Promise<void> {
if (styles && styles.length > 0) {
const all = styles.map(styleUrl => {
if (this.cache.has(styleUrl)) {
return false;
}
return new Promise((resolve, reject) => {
const url = this.parseUrl(styleUrl);
const linkDom = document.createElement('link');
linkDom.setAttribute('type', 'text/css');
linkDom.setAttribute('rel', 'stylesheet');
linkDom.setAttribute('href', url);
linkDom.onload = resolve;
linkDom.onerror = reject;
document.head.appendChild(linkDom);
protected async loadStyles(
remotePlugin: RemotePluginItem,
rootJsPath: string,
): Promise<void> {
const { styles } = remotePlugin.config;
if (styles) {
const arr = styles instanceof Array ? styles : [styles];
if (arr && arr.length > 0) {
const folder = dirname(rootJsPath);
const all = arr.map(styleUrl => {
const url = `${join(folder, basename(styleUrl))}`;
if (this.cache.has(url)) {
return false;
}
return new Promise((resolve, reject) => {
const linkDom = document.createElement('link');
linkDom.setAttribute('type', 'text/css');
linkDom.setAttribute('rel', 'stylesheet');
linkDom.setAttribute('href', url);
linkDom.onload = resolve;
linkDom.onerror = reject;
document.head.appendChild(linkDom);
});
});
});
await Promise.all(all);
await Promise.all(all);
}
}
}
......@@ -173,6 +216,6 @@ export class PluginFactory implements IPluginFactory {
if (script.startsWith('http://') || script.startsWith('https://')) {
return script;
}
return `${ibiz.env.pluginBaseUrl}${script}`;
return `./${join(ibiz.env.pluginBaseUrl, script)}`;
}
}
......@@ -15,9 +15,9 @@ import {
* @implements {EditorProvider}
*/
export class CheckBoxListEditorProvider implements IEditorProvider {
formEditor: string = 'IBizCheckBoxList';
formEditor: string = 'IBizCheckbox';
rowEditor: string = 'IBizCheckBoxList';
gridEditor: string = 'IBizGridCheckbox';
async createController(
editorModel: CheckBoxListModel,
......
......@@ -17,7 +17,7 @@ import {
export class DataPickerEditorProvider implements IEditorProvider {
formEditor: string;
rowEditor: string;
gridEditor: string;
constructor(editorType: string) {
let componentName = 'IBizPicker';
......@@ -26,7 +26,7 @@ export class DataPickerEditorProvider implements IEditorProvider {
componentName = 'IBizPickerDropDown';
break;
case 'PICKEREX_LINK':
componentName = 'AppPickerLinkOnly';
componentName = 'IBizPickerLink';
break;
case 'ADDRESSPICKUP':
componentName = 'IBizMPicker';
......@@ -34,7 +34,7 @@ export class DataPickerEditorProvider implements IEditorProvider {
default:
}
this.formEditor = componentName;
this.rowEditor = componentName;
this.gridEditor = componentName;
}
async createController(
......
......@@ -18,7 +18,7 @@ import {
export class DatePickerEditorProvider implements IEditorProvider {
formEditor: string = 'IBizDatePicker';
rowEditor: string = 'IBizDatePicker';
gridEditor: string = 'IBizDatePicker';
async createController(
editorModel: DatePickerEditorModel,
......
此差异已折叠。
此差异已折叠。
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册