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

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

上级 17f9e35b
// dynamic front template
\ No newline at end of file
FROM registry.cn-shanghai.aliyuncs.com/ibizops/nginx-dynamic:v1
WORKDIR /
COPY dist /dist
\ No newline at end of file
# full-dynamic-vue
全动态版本vue,vite + vue2.7 + iview4.7 + tsx
\ No newline at end of file
/**
* 提交信息规范
* 提交格式: type: description
*
* feat: 新功能(feature)
* fix: 修补bug
* docs: 文档(documentation)
* style: 格式(不影响代码运行的变动)
* refactor: 重构(即不是新增功能,也不是修改bug的代码变动)
* test: 增加测试,或测试变更
* perf : 性能优化
* revert: 撤销上一次的提交
* build: 构建工具或构建过程等的变动,如:关联包升级等
* chore: 其他修改(不在上述类型中的修改)
* release: 发布新版本
*/
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'test',
'perf',
'revert',
'build',
'chore',
'release',
],
],
},
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link type="text/css" href="./css/app-loading.css" rel="stylesheet">
<script type="javascript" src="./environments/environment.js"></script>
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<div id="app-loading-x" class="app-loading-x">
<div class="app-loading-x-container">
<label></label>
<label></label>
<label></label>
<label></label>
<label></label>
<label></label>
</div>
</div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
pnpm link --global "@ibiz-template/vue-util"
pnpm link --global "@ibiz-template/service"
pnpm link --global "@ibiz-template/runtime"
pnpm link --global "@ibiz-template/model"
pnpm link --global "@ibiz-template/core"
pnpm link --global "@ibiz-template/command"
pnpm link --global "@ibiz-template/controller"
\ No newline at end of file
server {
listen 80;
server_name localhost;
location /web/ {
alias /dist/;
index index.html index.htm;
}
location /trainsys__web {
proxy_pass http://gateway.ibizcloud.cn:20086;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
\ No newline at end of file
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint 'src/**/*.ts' 'src/**/*.vue'",
"lint:style": "stylelint 'src/**/*.scss'",
"prepare": "husky install"
},
"dependencies": {
"@ibiz-template/command": "^0.0.1-alpha.2",
"@ibiz-template/controller": "^0.0.1-alpha.5",
"@ibiz-template/core": "^0.0.1-alpha.5",
"@ibiz-template/model": "^0.0.1-alpha.5",
"@ibiz-template/runtime": "^0.0.1-alpha.5",
"@ibiz-template/service": "^0.0.1-alpha.5",
"@ibiz-template/vue-util": "^0.0.1-alpha.5",
"lodash-es": "^4.17.21",
"pinia": "^2.0.22",
"proxy-polyfill": "^0.3.2",
"qs": "^6.11.0",
"qx-util": "^0.4.1",
"ramda": "^0.28.0",
"systemjs": "^6.12.6",
"view-design": "^4.7.0",
"vue": "^2.7.10",
"vue-router": "^3.6.4"
},
"devDependencies": {
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.7.18",
"@types/qs": "^6.9.7",
"@types/ramda": "^0.28.15",
"@types/systemjs": "^6.1.1",
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"@vitejs/plugin-legacy": "^2.2.0",
"@vitejs/plugin-vue2": "^2.0.0",
"@vitejs/plugin-vue2-jsx": "^1.0.3",
"@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
"eslint": "^8.23.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.5.1",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"postcss": "^8.4.16",
"prettier": "^2.7.1",
"rollup-plugin-visualizer": "^5.8.1",
"sass": "^1.55.0",
"stylelint": "^14.12.1",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recess-order": "^3.0.0",
"stylelint-config-standard": "^28.0.0",
"stylelint-config-standard-scss": "^5.0.0",
"stylelint-scss": "^4.3.0",
"terser": "^5.15.0",
"typescript": "^4.8.3",
"vite": "^3.1.3",
"vite-plugin-eslint": "^1.8.1",
"vue-eslint-parser": "^9.1.0",
"vue-tsc": "^0.40.13"
},
"lint-staged": {
"*.{ts,vue}": "eslint --fix",
"*.less": "stylelint --syntax=scss"
}
}
因为 它太大了无法显示 源差异 。您可以改为 查看blob
@charset "utf-8";
.app-loading-x {
position: absolute;
top: 0;
left: 0;
z-index: 10000;
width: 100%;
height: 100%;
overflow: hidden;
background: radial-gradient(#55A0FE, #1767CB);
}
.app-loading-x-container {
position: absolute;
top: 50%;
width: 100%;
color: #fff;
text-align: center;
transform: translateY(-50%);
}
.app-loading-x-container label {
display: inline-block;
font-size: 20px;
opacity: 0;
}
.app-loading-x-container label:nth-child(6) {
animation: loading 3s infinite ease-in-out
}
.app-loading-x-container label:nth-child(5) {
animation: loading 3s .1s infinite ease-in-out
}
.app-loading-x-container label:nth-child(4) {
animation: loading 3s .2s infinite ease-in-out
}
.app-loading-x-container label:nth-child(3) {
animation: loading 3s .3s infinite ease-in-out
}
.app-loading-x-container label:nth-child(2) {
animation: loading 3s .4s infinite ease-in-out
}
.app-loading-x-container label:nth-child(1) {
animation: loading 3s .5s infinite ease-in-out
}
@keyframes loading{
0% {
opacity:0;
transform:translateX(-300px)
}
33% {
opacity:1;
transform:translateX(0)
}
66% {
opacity:1;
transform:translateX(0)
}
100% {opacity:0;
transform:translateX(300px)
}
}
\ No newline at end of file
window.Environment = {
// 远端动态基础路径
remoteDynaPath: '/remotemodel',
// 应用请求基础路径
BaseUrl: 'pms__sclpmswebapp',
// 中心系统标识
mockDcSystemId: 'pms',
};
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
import { defineComponent } from 'vue';
export default defineComponent({
render() {
return (
<div class='app'>
<router-view />
</div>
);
},
});
import { VueConstructor } from 'vue';
import { install as installCore } from '@ibiz-template/core';
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 ViewDesign from 'view-design';
import 'view-design/dist/styles/iview.css';
import {
AppLayout,
ControlLayout,
ViewBase,
ViewLayout,
} from './components/layout';
import {
AppMenu,
EditFormControl,
FormControl,
FormPage,
FormPageItem,
FormGroupPanel,
FormItemContainer,
FormItem,
GridControl,
SearchFormControl,
FormTabPanel,
FormTabPage,
FormRawItem,
FormButton,
FormDRUIPart,
GridColumn,
GridUAColumn,
GridFieldColumn,
GridEditItem,
ViewPanel,
PickupViewPanel,
} from './components/widgets';
import {
EditView,
GridView,
PickupGridView,
PickupView,
MPickupView,
OptView,
} from './components/views';
import { IndexView } from './views';
import AppKeepAlive from './components/common/app-keep-alive/app-keep-alive.vue';
import {
AppIcon,
ViewToolbar,
QuickSearch,
AppCol,
AppRow,
AppGridPagination,
AppGridEditItem,
ActionToolbar,
AppUser,
AppTransition,
} 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 { presetEditorProvider } from './provider';
export const AppRegister = {
install(v: VueConstructor) {
installCore();
installService();
installRuntime();
installController();
presetEditorProvider();
v.use(ViewDesign);
// 注册布局组件
v.component('AppLayout', AppLayout);
v.component('ControlLayout', ControlLayout);
v.component('ViewLayout', ViewLayout);
v.component('ViewBase', ViewBase);
// 注册视图组件
v.component('IndexView', IndexView);
v.component('GridView', GridView);
v.component('EditView', EditView);
v.component('OptView', OptView);
v.component('PickupGridView', PickupGridView);
v.component('PickupView', PickupView);
v.component('MPickupView', MPickupView);
// 注册部件组件
v.component('AppMenu', AppMenu);
v.component('GridControl', GridControl);
v.component('GridColumn', GridColumn);
v.component('GridUaColumn', GridUAColumn);
v.component('GridFieldColumn', GridFieldColumn);
v.component('GridEditItem', GridEditItem);
v.component('FormButton', FormButton);
v.component('FormDruipart', FormDRUIPart);
v.component('FormGroupPanel', FormGroupPanel);
v.component('AppFormItem', FormItem);
v.component('FormPage', FormPage);
v.component('FormPageItem', FormPageItem);
v.component('FormRawItem', FormRawItem);
v.component('FormTabPage', FormTabPage);
v.component('FormTabPanel', FormTabPanel);
v.component('FormControl', FormControl);
v.component('EditFormControl', EditFormControl);
v.component('SearchFormControl', SearchFormControl);
v.component('ViewPanel', ViewPanel);
v.component('PickupViewPanel', PickupViewPanel);
// 注册通用组件
v.component('AppKeepAlive', AppKeepAlive);
v.component('AppIcon', AppIcon);
v.component('AppCol', AppCol);
v.component('AppRow', AppRow);
v.component('ViewToolbar', ViewToolbar);
v.component('ActionToolbar', ActionToolbar);
v.component('AppGridPagination', AppGridPagination);
v.component('FormItemContainer', FormItemContainer);
v.component('AppGridEditItem', AppGridEditItem);
v.component('AppUser', AppUser);
v.component('AppTransition', AppTransition);
// 注册编辑器组件
v.component('IBizSpan', IBizSpan);
v.component('IBizInputBox', IBizInputBox);
v.component('AppInputNumber', AppInputNumber);
v.component('IBizCheckBoxList', IBizCheckBoxList);
v.component('IBizRadioButtonList', IBizRadioButtonList);
v.component('IBizDatePicker', IBizDatePicker);
v.component('IBizDropDownList', IBizDropDownList);
v.component('IBizPicker', IBizPicker);
v.component('IBizPickerDropDown', IBizPickerDropDown);
v.component('AppPickerLinkOnly', AppPickerLinkOnly);
v.component('IBizMPicker', IBizMPicker);
v.component('IBizFileUpload', IBizFileUpload);
v.component('AppFileUploadRowPreview', AppFileUploadRowPreview);
v.component('AppImageUpload', AppImageUpload);
v.component('AppSelectTree', AppSelectTree);
v.component('QuickSearch', QuickSearch);
v.component('NotSupportedEditor', NotSupportedEditor);
},
};
export async function attachEnvironmentConfig(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const env = (window as any).Environment;
ibiz.env.baseUrl = env.BaseUrl;
ibiz.env.remoteModelUrl = env.remoteDynaPath;
ibiz.env.dcSystem = env.mockDcSystemId;
}
import { defineComponent, PropType, reactive } from 'vue';
import { IPSUIActionGroup, IPSUIActionGroupDetail } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/common/view-toolbar/view-toolbar.scss';
import { ActionStates, ActionState } from '@ibiz-template/controller';
export const ActionToolbar = defineComponent({
name: 'ActionToolbar',
props: {
actionGroup: {
type: Object as PropType<IPSUIActionGroup>,
required: true,
},
actionStates: {
type: Object as PropType<ActionStates>,
required: true,
},
},
setup(props, { emit }) {
const ns = useNamespace('action-toolbar');
// 按钮状态控制
const actionStates = reactive<{ [p: string]: ActionState }>({});
props.actionGroup.getPSUIActionGroupDetails()?.forEach(detail => {
const action = detail.getPSUIAction();
if (action) {
actionStates[action.uIActionTag] = { disabled: false };
}
});
// 点击事件抛给表格执行
const handleClick = async (
detail: IPSUIActionGroupDetail,
event: MouseEvent,
) => {
emit('action-click', detail, event);
};
return { ns, handleClick };
},
render() {
const details = this.actionGroup.getPSUIActionGroupDetails() || [];
return (
<div class={`${this.ns.b()}`}>
{details.length > 0 &&
details.map(detail => {
const action = detail.getPSUIAction();
if (action) {
return (
<i-button
type='text'
on-click={(e: MouseEvent) => this.handleClick(detail, e)}
disabled={this.actionStates[action.uIActionTag].disabled}
class={[this.ns.be('item'), this.ns.is('disabled', false)]}
>
{detail.showIcon && action.getPSSysImage() && (
<app-icon icon={action.getPSSysImage()}></app-icon>
)}
{detail.showCaption ? action!.caption : ''}
</i-button>
);
}
return null;
})}
</div>
);
},
});
import { computed, defineComponent, PropType } from 'vue';
import {
IPSFlexLayoutPos,
IPSGridLayoutPos,
IPSLayoutPos,
} from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
export const AppCol = defineComponent({
name: 'AppCol',
props: {
layoutPos: Object as PropType<IPSLayoutPos>,
modelData: {
type: Object as PropType<IData>,
required: true,
},
},
setup(props) {
const ns = useNamespace('col');
// 计算栅格布局的属性
const gridAttrs = computed(() => {
const gridLayoutPos = props.layoutPos as IPSGridLayoutPos;
// FLEX布局时不计算
if (gridLayoutPos.layout === 'FLEX') {
return {};
}
// 计算倍率,12列栅格为2
const {
colXS,
colXSOffset,
colSM,
colSMOffset,
colMD,
colMDOffset,
colLG,
colLGOffset,
} = gridLayoutPos;
const multiplier = gridLayoutPos.layout === 'TABLE_24COL' ? 1 : 2;
return {
xs: { span: colXS * multiplier, offset: colXSOffset * multiplier },
sm: { span: colSM * multiplier, offset: colSMOffset * multiplier },
md: { span: colMD * multiplier, offset: colMDOffset * multiplier },
lg: { span: colLG * multiplier, offset: colLGOffset * multiplier },
};
});
const cssVars = computed(() => {
const styles = {};
Object.assign(styles, {
width: props.modelData.width ? props.modelData.width : '100%',
height: props.modelData.height ? props.modelData.height : '100%',
});
return styles;
});
return { ns, gridAttrs, cssVars };
},
render(h) {
if (this.layoutPos?.layout === 'FLEX') {
return (
<div
class={[this.ns.b(), this.ns.m('flex')]}
style={{
flexGrow: (this.layoutPos as IPSFlexLayoutPos).grow,
...this.cssVars,
}}
>
{this.$slots.default}
</div>
);
}
return h(
'i-col',
{
class: [this.ns.b(), this.ns.m('grid')],
props: this.gridAttrs,
style: this.cssVars,
},
this.$slots.default,
);
},
});
import { defineComponent } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
export const AppGridEditItem = defineComponent({
name: 'AppGridEditItem',
props: {
required: {
type: Boolean,
},
error: {
type: String || null,
},
},
setup() {
const ns = useNamespace('grid-edit-item');
return { ns };
},
render() {
return (
<div class={this.ns.b()}>
{!this.error ? (
this.$slots.default
) : (
<tooltip
content={this.error}
transfer
transfer-class-name='grid-error'
placement='top'
style='border: 1px solid red'
>
{this.$slots.default}
</tooltip>
)}
</div>
);
},
});
export default AppGridEditItem;
import { defineComponent } from 'vue';
import '@/styles/components/common/app-grid-pagination/app-grid-pagination.scss';
import { useNamespace } from '@ibiz-template/vue-util';
export const AppGridPagination = defineComponent({
name: 'AppGridPagination',
props: {
total: {
type: Number,
},
curPage: {
type: Number,
},
size: {
type: Number,
},
},
setup() {
const ns = useNamespace('grid-page');
return { ns };
},
methods: {
onPageChange(page: number) {
this.$emit('change', page);
},
onPageSizeChange(size: number) {
this.$emit('page-size-change', size);
},
pageReset() {
this.$emit('page-reset');
},
},
render() {
return (
<div class={this.ns.b()}>
<page
transfer={true}
total={this.total}
show-sizer
show-elevator
current={this.curPage}
page-size={this.size}
page-size-opts={[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]}
show-total
on-on-change={this.onPageChange}
on-on-page-size-change={this.onPageSizeChange}
>
<span class={this.ns.b('btn')}>
<i-button
icon='md-refresh'
title='刷新'
on-click={this.pageReset}
></i-button>
</span>
<span>共计&nbsp;{this.total}&nbsp;条数据</span>
</page>
</div>
);
},
});
export default AppGridPagination;
import { IPSSysImage } from '@ibiz-template/model';
import { computed, defineComponent, PropType } from 'vue';
export const AppIcon = defineComponent({
name: 'AppIcon',
props: {
icon: {
type: Object as PropType<IPSSysImage>,
},
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
},
},
setup(props) {
const classNames = computed(() => {
let className = ' app-icon';
if (props.size) {
className += ` app-icon${props.size}`;
}
return className;
});
const BaseUrl = `${ibiz.env.assetsUrl}/imgs/`;
return { classNames, BaseUrl };
},
render() {
if (this.icon) {
if (this.icon.cssClass) {
return <i class={this.icon.cssClass + this.classNames} />;
}
if (this.icon.imagePath) {
return (
<img
class={this.classNames}
src={this.BaseUrl + this.icon.imagePath}
/>
);
}
}
return null;
},
});
export default AppIcon;
<script>
export const patternTypes = [String, RegExp, Array];
export default {
name: 'AppKeepAlive',
props: {
// 根据组件 name 进行匹配。如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
include: patternTypes,
exclude: patternTypes,
max: [String, Number],
keyList: [Array],
},
data() {
return {
// eslint-disable-next-line vue/no-reserved-keys
_toString: Object.prototype.toString,
};
},
watch: {
include(val) {
const _this = this;
_this.pruneCache(function (name) {
return _this.matches(val, name);
});
},
exclude(val) {
const _this = this;
_this.pruneCache(function (name) {
return !_this.matches(val, name);
});
},
keyList(val) {
const _this = this;
// 配置了keyList但是下方插件key
_this.pruneCache2(function (name) {
return !_this.matches(val, name);
});
},
},
created() {
// 保存缓存的组件
this.cache = Object.create(null);
// 保存缓存的组件的key
this.keys = [];
},
destroyed() {
const _this = this;
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in _this.cache) {
_this.pruneCacheEntry(_this.cache, key, _this.keys);
}
},
methods: {
pruneCacheEntry(cache, key, keys, _current) {
const cached = cache[key];
if (cached) {
cached.componentInstance.$destroy();
}
// eslint-disable-next-line no-param-reassign
cache[key] = null;
this.remove(keys, key);
},
pruneCache(filter) {
const _this = this;
const cache = _this.cache;
const keys = _this.keys;
const _vnode = _this._vnode;
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in cache) {
const cachedNode = cache[key];
if (cachedNode) {
const name = _this.getComponentName(cachedNode.componentOptions);
if (name && !filter(name)) {
_this.pruneCacheEntry(cache, key, keys, _vnode);
}
}
}
},
pruneCache2(filter) {
const _this = this;
const cache = _this.cache;
const keys = _this.keys;
const _vnode = _this._vnode;
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in cache) {
const cachedNode = cache[key];
if (cachedNode) {
const name = cachedNode.data.curPath;
if (name && filter(name)) {
_this.pruneCacheEntry(cache, key, keys, _vnode);
}
}
}
},
matches(pattern, name) {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1;
}
if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1;
}
if (this.isRegExp(pattern)) {
return pattern.test(name);
}
/* istanbul ignore next */
return false;
},
getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag);
},
getFirstComponentChild(children) {
const _this = this;
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const c = children[i];
if (
_this.isDef(c) &&
(_this.isDef(c.componentOptions) || _this.isAsyncPlaceholder(c))
) {
return c;
}
}
}
},
isAsyncPlaceholder(node) {
return node.isComment && node.asyncFactory;
},
isDef(v) {
return v !== undefined && v !== null;
},
isRegExp(v) {
return this._toString.call(v) === '[object RegExp]';
},
remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
},
},
render: function render() {
// this.$slots.default 包含了所有没有被包含在具名插槽中的节点
// 这里取得第一个子组件
const _this = this;
const slot = _this.$slots.default;
const vnode = _this.getFirstComponentChild(slot);
const componentOptions = vnode && vnode.componentOptions;
if (componentOptions) {
// check pattern
const name = _this.getComponentName(componentOptions);
const ref = _this;
const include = ref.include;
const exclude = ref.exclude;
const keyList = ref.keyList;
// 获取第一个子组件上面的key
const slotKey = vnode.key;
// 如果 componentName 没有作为keep-alive被包含进来,直接返回
if (
// 包括并且不匹配的
(include && (!name || !_this.matches(include, name))) ||
// 排除并且匹配的
(exclude && name && _this.matches(exclude, name)) ||
// keyList中存在并且不匹配的
(keyList && !slotKey && !_this.matches(keyList, slotKey))
) {
return vnode;
}
const ref$1 = _this;
const cache = ref$1.cache;
const keys = ref$1.keys;
const key =
vnode.key == null
? // 相同的构造器(constructor)可能会注册为不同的本地组件,所以仅有一个 cid 是不够的
componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key;
if (cache[key]) {
// 如果已经缓存了,需要保持当前的key 是最新的
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
_this.remove(keys, key);
keys.push(key);
} else {
// 否则,进行缓存并更新keys数组。
cache[key] = vnode;
keys.push(key);
// 缓存组件超出最大值,将队首的组件销毁(先进先出)
if (_this.max && keys.length > parseInt(_this.max, 10)) {
_this.pruneCacheEntry(cache, keys[0], keys, _this._vnode);
}
}
vnode.data.keepAlive = true;
vnode.data.curPath = slotKey;
}
return vnode || (slot && slot[0]);
},
};
</script>
import { IPSFlexLayout, IPSLayout } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, PropType } from 'vue';
export const AppRow = defineComponent({
name: 'AppRow',
props: {
layout: Object as PropType<IPSLayout>,
},
setup() {
const ns = useNamespace('row');
return { ns };
},
render() {
if (this.layout?.layout === 'FLEX') {
const { dir, align, vAlign } = this.layout as IPSFlexLayout;
return (
<div
class={[this.ns.b(), this.ns.m('flex')]}
style={`display:flex;flex-direction: ${dir};justify-content: ${align};align-items: ${vAlign};`}
>
{this.$slots.default}
</div>
);
}
return (
<row class={[this.ns.b(), this.ns.m('grid')]}>{this.$slots.default}</row>
);
},
});
<script setup lang="ts">
import { ref } from 'vue';
import '@/styles/components/common/app-transition/app-transition.scss';
const transName = ref('slide-right');
</script>
<template>
<Transition :name="transName" mode="out-in">
<slot></slot>
</Transition>
</template>
import { defineComponent } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/common/app-user/app-user.scss';
export const AppUser = defineComponent({
name: 'AppUser',
setup() {
const ns = useNamespace('app-user');
const { srfusername = '游客' } = ibiz.appData?.context || {};
return { ns, srfusername };
},
methods: {
async onClick() {
const res = await ibiz.auth.v7logout();
if (res.ok) {
console.log('登出成功');
this.$router.push(
`/login?ru=${encodeURIComponent(
window.location.hash.replace('#/', '/'),
)}`,
);
} else {
console.log('登出失败');
}
},
},
render() {
return (
<div class={this.ns.b()}>
<dropdown class={this.ns.b('avatar')}>
<span class={this.ns.b('avatar-wrapper')}>
<avatar
size='small'
src='https://i.loli.net/2017/08/21/599a521472424.jpg'
/>
<span class={this.ns.be('avatar', 'name')}>{this.srfusername}</span>
</span>
<dropdownMenu slot='list'>
<dropdownItem>
<i class='ivu-icon ivu-icon-ios-log-out'></i>
<span on-click={this.onClick}>退出登录</span>
</dropdownItem>
</dropdownMenu>
</dropdown>
</div>
);
},
});
import AppIcon from './app-icon/app-icon';
import QuickSearch from './quick-search/quick-search';
import { ViewToolbar } from './view-toolbar/view-toolbar';
import { AppCol } from './app-col/app-col';
import { AppRow } from './app-row/app-row';
import AppGridPagination from './app-grid-pagination/app-grid-pagination';
import AppGridEditItem from './app-grid-edit-item/app-grid-edit-item';
import { ActionToolbar } from './action-toolbar/action-toolbar';
import { AppUser } from './app-user/app-user';
import AppTransition from './app-transition/app-transition.vue';
export {
AppIcon,
ViewToolbar,
AppCol,
AppRow,
QuickSearch,
AppGridPagination,
AppGridEditItem,
ActionToolbar,
AppUser,
AppTransition,
};
import { computed, defineComponent, watch } from 'vue';
import { debounce } from 'lodash-es';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/common/quick-search/quick-search.scss';
export const QuickSearch = defineComponent({
name: 'QuickSearch',
props: {
value: {
type: String,
},
viewMode: {
type: String,
required: true,
},
},
setup(props, { emit }) {
const ns = useNamespace('quick-search');
const valu = computed({
get() {
return props.value;
},
set(val: string | undefined) {
emit('update', val!);
},
});
const search = debounce(() => {
emit('search');
}, 500);
const onChange = (e: InputEvent) => {
if (e.target) {
valu.value = (e.target as unknown as { value: string }).value;
}
};
watch(
() => props.value,
() => {
search();
},
);
return { ns, valu, search, onChange };
},
render() {
return (
<i-input
value={this.value}
class={[this.ns.b(), this.ns.m(this.viewMode.toLowerCase())]}
search={true}
on-on-change={this.onChange}
on-on-enter={this.search}
on-on-search={this.search}
></i-input>
);
},
});
export default QuickSearch;
import SkeletonCard from './skeleton-card/skeleton-card';
import SkeletonElement from './skeleton-element/skeleton-element';
import SkeletonInput from './skeleton-input/skeleton-input';
export { SkeletonCard, SkeletonElement, SkeletonInput };
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, onUnmounted, ref, watch } from 'vue';
import type { PropType } from 'vue';
import '@/styles/components/common/skeleton/skeleton-card/skeleton-card.scss';
export default defineComponent({
props: {
active: {
type: Boolean,
default: false,
},
round: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: true,
},
title: {
type: Boolean,
default: true,
},
paragraph: {
type: [Boolean, Object] as PropType<boolean | { rows: number }>,
default: true,
},
},
setup(props, { slots }) {
const ns = useNamespace('skeleton-card');
const content = ref<HTMLElement | null>(null);
const rows = ref<number>(2);
watch(
() => props.paragraph,
value => {
if (!(typeof value === 'boolean')) {
rows.value = Math.floor(value.rows > 2 ? value.rows : 2);
}
},
);
const childHeight = 16;
const childMarginBottom = 12;
let observer: ResizeObserver | null = null;
watch(
content,
() => {
if (content.value && !observer && ResizeObserver) {
observer = new ResizeObserver(entries => {
if (typeof props.paragraph === 'boolean') {
rows.value = Math.floor(
(entries[0].contentRect.height + childMarginBottom) /
(childHeight + childMarginBottom),
);
}
});
observer.observe(content.value);
} else if (!content.value && observer) {
observer.disconnect();
observer = null;
}
},
{ immediate: true },
);
onUnmounted(() => {
if (observer) {
observer.disconnect();
observer = null;
}
});
return () => (
<div
class={`${props.loading ? ns.b() : ''} ${
props.loading && props.active ? ns.m('active') : ''
} ${props.loading && props.round ? ns.m('round') : ''}`.trim()}
>
{props.loading && props.title ? (
<header class={ns.b('header')}>
<h3 class={ns.be('header', 'title')}></h3>
</header>
) : null}
{props.loading && props.paragraph ? (
<main class={ns.b('content')} ref={content as unknown as string}>
{Array.from({ length: rows.value }).map((_value, i) => (
<div class={ns.be('content', 'item')} key={i}></div>
))}
</main>
) : null}
{typeof slots.default === 'function' && !props.loading
? slots.default()
: null}
</div>
);
},
});
import { computed, defineComponent } from 'vue';
import type { PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/common/skeleton/skeleton-element/skeleton-element.scss';
export default defineComponent({
props: {
prefixClass: String,
size: [String, Number] as PropType<
'large' | 'medium' | 'small' | 'default' | number
>,
shape: String as PropType<'circle' | 'square' | 'round' | 'default'>,
active: Boolean,
},
setup(props) {
const ns = useNamespace(`skeleton-${props.prefixClass}`);
const sizeClass = computed(() => {
return typeof props.size === 'string' && props.size !== 'default'
? ns.m(props.size)
: '';
});
const shapeClass = computed(() => {
return props.shape && props.shape !== 'default' ? ns.m(props.shape) : '';
});
const sizeStyle = computed(() => {
return typeof props.size === 'number'
? {
width: `${props.size}px`,
height: `${props.size}px`,
lineHeight: `${props.size}px`,
}
: {};
});
return () => (
<span
class={`${ns.b()} ${sizeClass.value} ${shapeClass.value}`.trim()}
style={sizeStyle.value}
></span>
);
},
});
import { defineComponent } from 'vue';
import type { PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
export default defineComponent({
props: {
size: {
type: String as PropType<'large' | 'medium' | 'small' | 'default'>,
default: 'default',
},
active: Boolean,
},
setup(props) {
const ns = useNamespace('skeleton-element');
return () => (
<div class={`${ns.b()} ${props.active ? ns.m('active') : ''}`.trim()}>
<skeleton-element prefixClass='input' props={props}></skeleton-element>
</div>
);
},
});
import { computed, defineComponent, onMounted, ref } from 'vue';
import {
IPSDETBGroupItem,
IPSDETBUIActionItem,
IPSDEToolbarItem,
ToolbarModel,
} from '@ibiz-template/model';
import { ToolbarNeuron } from '@ibiz-template/controller';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/common/view-toolbar/view-toolbar.scss';
const btnContent = (item: IPSDEToolbarItem, viewMode: string) => {
const image = item.getPSSysImage();
if (viewMode === 'EMBED') {
if (image) {
return [<app-icon icon={image} />, item.caption];
}
return [<img src='undefined' />, item.caption];
}
return [<app-icon icon={image} />, item.caption];
};
export const ViewToolbar = defineComponent({
name: 'ViewToolbar',
props: {
modelData: {
type: ToolbarModel,
required: true,
},
viewMode: {
type: String,
required: true,
},
},
setup(props, { emit }) {
const ns = useNamespace('view-toolbar');
const neuron = new ToolbarNeuron({});
emit('neuronInit', neuron);
// 正在执行的工具栏项
const doingToolbarItem = ref<string>('');
onMounted(async () => {
await neuron.evt.asyncEmit('mounted');
});
const handleClick = async (item: IPSDEToolbarItem, _event: MouseEvent) => {
doingToolbarItem.value = item.id;
try {
await neuron.evt.asyncEmit(
'itemClick',
item as IPSDETBUIActionItem,
_event,
);
} finally {
doingToolbarItem.value = '';
}
};
const btnSize = computed(() => {
return props.viewMode === 'EMBED' ? 'small' : 'default';
});
return { ns, doingToolbarItem, handleClick, btnSize };
},
render() {
return (
<div class={[this.ns.b(), this.ns.m(this.viewMode.toLowerCase())]}>
{this.modelData!.source.getPSDEToolbarItems()?.map(item => {
if (item.itemType === 'SEPERATOR') {
return (
<div key={item.id} class={this.ns.b('item')}>
|
</div>
);
}
if (item.itemType === 'RAWITEM') {
return (
<div key={item.id} class={this.ns.b('item')}>
{btnContent(item, this.viewMode)}
</div>
);
}
if (item.itemType === 'DEUIACTION') {
return (
<div key={item.id} class={this.ns.b('item')}>
<i-button
title={item.tooltip}
size={this.btnSize}
loading={
!!this.doingToolbarItem && this.doingToolbarItem === item.id
}
disabled={!!this.doingToolbarItem}
on-click={(e: MouseEvent) => this.handleClick(item, e)}
>
{btnContent(item, this.viewMode)}
</i-button>
</div>
);
}
if (item.itemType === 'ITEMS') {
return (
<div key={item.id} class={this.ns.b('item')}>
<dropdown trigger='click' placement='bottom-start'>
<i-button title={item.tooltip} size={this.btnSize}>
{btnContent(item, this.viewMode)}
</i-button>
<dropdown-menu slot='list'>
{(item as IPSDETBGroupItem)
.getPSDEToolbarItems()!
.map(item2 => {
return (
<dropdown-item
key={item2.id}
nativeOn-click={(e: MouseEvent) =>
this.handleClick(item2, e)
}
>
{btnContent(item2, this.viewMode)}
</dropdown-item>
);
})}
</dropdown-menu>
</dropdown>
</div>
);
}
return null;
})}
</div>
);
},
});
import { UploadEditorController } from '@ibiz-template/controller';
import { defineComponent, onMounted, ref, watch } from 'vue';
import type { PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import { getCookie } from 'qx-util';
export default defineComponent({
props: {
value: String,
controller: {
type: UploadEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
emits: {
change: (_value: string | null) => true,
},
setup(props, { emit }) {
const ns = useNamespace('app-file-upload-row-preview');
const c = props.controller;
// 文件列表
const files = ref<IData[]>([]);
// 请求头
const headers = ref<IData>({});
// 上传文件路径
const uploadUrl = ref<string>(ibiz.env.baseUrl + ibiz.env.UploadFile);
// 下载文件路径
const downloadUrl = ref<string>(ibiz.env.baseUrl + ibiz.env.ExportFile);
// 模态框是否显示
const dialogVisible = ref<boolean>(false);
// 行为是否显示
const showActions = ref<boolean>(false);
const onDialogVisibleChange = (value: boolean) => {
dialogVisible.value = value;
};
// 设置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;
console.log(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) => {
let tempDownloadUrl: string = url;
const BaseUrl = ibiz.env.baseUrl;
if (url.startsWith(BaseUrl)) {
tempDownloadUrl = url.replace(BaseUrl, '');
}
// 发送get请求
ibiz.net
.get(
tempDownloadUrl,
{},
{},
{
responseType: 'blob',
},
)
.then((response: IData) => {
if (!response || response.status !== 200) {
console.log('error');
return;
}
// 请求成功,后台返回的是一个文件流
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 {
console.log('error');
}
})
.catch(error => {
console.error(error);
});
};
// 下载文件
const onDownload = (file: IData) => {
const url = `${downloadUrl.value}/${file.id}`;
DownloadFile(url, file);
};
return {
ns,
c,
files,
headers,
uploadUrl,
dialogVisible,
showActions,
onDialogVisibleChange,
beforeUpload,
onSuccess,
onError,
onRemove,
onDownload,
};
},
render(h) {
return (
<div class={this.ns.b()}>
<row>
{this.files.length > 0 ? (
<i-col span={12}>
<i-button
on-click={() => {
this.dialogVisible = true;
}}
>
查看
<badge count={this.files.length}></badge>
</i-button>
</i-col>
) : null}
<i-col span={this.files.length > 0 ? 12 : 24}>
{h(
'upload',
{
ref: 'fileUploadRowPreview',
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,
'show-upload-list': false,
'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'>上传文件</i-button>],
)}
</i-col>
</row>
<modal
class-name={this.ns.b('upload-preview-modal')}
width='80%'
footer-hide={true}
value={this.dialogVisible}
on-on-visible-change={this.onDialogVisibleChange}
>
<ul class={this.ns.be('upload-preview-modal', 'ul')}>
{this.files.map((file, index) => (
<li key={index} class={this.ns.be('upload-preview-modal', 'li')}>
<div class={this.ns.be('upload-preview-modal', 'box')}>
<img
class={this.ns.be('upload-preview-modal', 'img')}
src={file.url}
/>
<div
class={this.ns.be('upload-preview-modal', 'actions')}
on-mouseenter={() => {
this.showActions = true;
}}
on-mouseleave={() => {
this.showActions = false;
}}
>
{this.showActions ? (
<span
class={this.ns.be('upload-preview-modal', 'download')}
>
<icon
type='ios-download-outline'
on-click={() => {
this.onDownload(file);
}}
></icon>
</span>
) : null}
{this.showActions ? (
<span
class={this.ns.be('upload-preview-modal', 'delete')}
>
<icon
type='ios-trash-outline'
on-click={() => {
this.onRemove(file, this.files);
}}
></icon>
</span>
) : null}
</div>
</div>
<div class={this.ns.be('upload-preview-modal', 'filename')}>
{file.name}
</div>
</li>
))}
</ul>
</modal>
</div>
);
},
});
import { UploadEditorController } from '@ibiz-template/controller';
import { defineComponent, onMounted, ref, watch } from 'vue';
import type { PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import { getCookie } from 'qx-util';
import '@/styles/components/editor/app-image-upload/app-image-upload.scss';
export default defineComponent({
props: {
value: String,
controller: {
type: UploadEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
emits: {
change: (_value: string | null) => true,
},
setup(props, { emit }) {
const ns = useNamespace('app-image-upload');
const c = props.controller;
// 文件列表
const files = ref<IData[]>([]);
// 请求头
const headers = ref<IData>({});
// 上传文件路径
const uploadUrl = ref<string>(ibiz.env.baseUrl + ibiz.env.UploadFile);
// 下载文件路径
const downloadUrl = ref<string>(ibiz.env.baseUrl + 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;
console.log(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) => {
let tempDownloadUrl: string = url;
const BaseUrl = ibiz.env.baseUrl;
if (url.startsWith(BaseUrl)) {
tempDownloadUrl = url.replace(BaseUrl, '');
}
// 发送get请求
ibiz.net
.get(
tempDownloadUrl,
{},
{},
{
responseType: 'blob',
},
)
.then((response: IData) => {
if (!response || response.status !== 200) {
console.log('error');
return;
}
// 请求成功,后台返回的是一个文件流
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 {
console.log('error');
}
})
.catch(error => {
console.error(error);
});
};
// 下载文件
const onDownload = (file: IData) => {
const url = file.url;
DownloadFile(url, file);
};
const dialogImageUrl = ref<string>('');
const dialogVisible = ref<boolean>(false);
const onDialogVisibleChange = (value: boolean) => {
dialogVisible.value = value;
};
// 预览
const onPreview = (file: IData) => {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
};
return {
ns,
c,
files,
headers,
uploadUrl,
dialogImageUrl,
dialogVisible,
beforeUpload,
onSuccess,
onError,
onRemove,
onDownload,
onDialogVisibleChange,
onPreview,
};
},
render(h) {
return (
<div class={this.ns.b()}>
<div class={this.ns.e('image-upload-list')}>
{this.files.map(item => (
<div key={item.id} class={this.ns.e('list-item')}>
<img src={item.url} />
<div class={this.ns.e('list-item-cover')}>
<icon
type='ios-eye-outline'
on-click={() => this.onPreview(item)}
></icon>
<icon
type='ios-download-outline'
on-click={() => this.onDownload(item)}
></icon>
<icon
type='ios-trash-outline'
on-click={() => this.onRemove(item, this.files)}
></icon>
</div>
</div>
))}
</div>
{h(
'upload',
{
ref: 'imageUpload',
props: {
action: this.uploadUrl,
headers: this.headers,
'default-file-list': this.files,
multiple: this.c.multiple,
type: 'drag',
accept: this.c.accept,
'show-upload-list': false,
'before-upload': this.beforeUpload,
'on-success': this.onSuccess,
'on-error': this.onError,
'on-remove': this.onRemove,
'on-preview': this.onDownload,
},
},
[
<div class={this.ns.e('btn')}>
<icon type='ios-add' size='30'></icon>
</div>,
],
)}
<modal
class-name={this.ns.b('upload-model')}
footer-hide={true}
value={this.dialogVisible}
on-on-visible-change={this.onDialogVisibleChange}
>
<img
class={this.ns.be('upload-model', 'model-img')}
src={this.dialogImageUrl}
/>
</modal>
</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 default defineComponent({
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('app-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()}>
<input-number
value={this.currentVal}
placeholder={this.c.placeHolder}
readonly={this.c.model.readOnly}
precision={this.c.model.precision}
on-on-change={this.handleChange}
></input-number>
</div>
);
},
});
import { PickerEditorController } from '@ibiz-template/controller';
import { defineComponent, PropType, ref, Ref, watch } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
export const AppPickerLinkOnly = defineComponent({
name: 'AppPickerLinkOnly',
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('app-picker-linkonly');
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>
);
},
});
export default AppPickerLinkOnly;
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()}>
<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>
<dropdownMenu slot='list'>
<tree data={this.treeNodes} show-checkbox></tree>
</dropdownMenu>
</dropdown>
</div>
);
},
});
export default AppSelectTree;
import { computed, defineComponent, ref } from 'vue';
import type { PropType } from 'vue';
import { CheckBoxListEditorController } from '@ibiz-template/controller';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/editor/ibiz-check-box-list/ibiz-check-box-list.scss';
export default defineComponent({
props: {
value: String,
controller: {
type: CheckBoxListEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
emits: {
change: (_value: null | string | number | string[]) => true,
},
setup(props, { emit }) {
const ns = useNamespace('check-box-list');
const c = props.controller;
const editorModel = c.model;
const codeList = editorModel.codeList;
// 代码表数据
const items = ref<readonly IData[]>([]);
c.loadCodeList(props.data).then(_codeList => {
items.value = _codeList;
});
// 当前模式
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 (props.value) {
if (Object.is(currentMode.value, 'num') && items) {
const selectsArray: Array<string | number> = [];
const num: number = parseInt(props.value, 10);
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')) {
if (props.value !== '') {
if (codeList) {
const selects: Array<string | number> =
props.value.split(valueSeparator);
if (codeList.codeItemValueNumber) {
for (let i = 0, len = selects.length; i < len; i++) {
selects[i] = Number(selects[i]);
}
}
return selects;
}
return props.value.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: unknown) => {
if (typeof item === 'string') {
// eslint-disable-next-line no-bitwise
temp |= parseInt(item, 10);
}
});
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()}>
<checkbox-group
value={this.selectArray}
on-on-change={this.onSelectArrayChange}
>
{this.items.map((_item, index: number) => (
<checkbox
key={index}
label={_item.value}
readonly={this.controller.model.readOnly}
>
<span class={this.ns.e('text')}>{_item.text}</span>
</checkbox>
))}
</checkbox-group>
</div>
);
},
});
import { DatePickerEditorController } from '@ibiz-template/controller';
import { ref, watch, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/editor/ibiz-date-picker/ibiz-date-picker.scss';
export const IBizDatePicker = defineComponent({
name: 'IBizDatePicker',
props: {
value: {
type: String,
},
controller: {
type: DatePickerEditorController,
},
data: {
type: Object as PropType<IData>,
},
disable: {
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 && newVal !== oldVal) {
currentVal.value = newVal;
}
},
{ 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()}>
<datePicker
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.disable}
></datePicker>
</div>
);
},
});
export default IBizDatePicker;
import { DropDownListEditorController } from '@ibiz-template/controller';
import { watch, ref, Ref, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/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>,
},
},
emits: {
change: (_value: string | Array<string>) => true,
},
setup(props, { emit }) {
const ns = useNamespace('dropdown-list');
const c = props.controller;
// 是否是树形
const hasChildren = ref(false);
// 当前值
const curValue: Ref<Array<string> | string | undefined> = ref('');
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal && newVal !== oldVal) {
curValue.value = c!.multiple ? props.value?.split(',') : props.value!;
}
},
{ immediate: true },
);
// 代码表
const items: Ref<readonly IData[]> = ref([]);
c!.loadCodeList(props.data!).then(codeList => {
items.value = codeList;
for (let i = 0; i < codeList.length; i++) {
const _item = codeList[i];
if (_item.children) {
hasChildren.value = true;
break;
}
}
});
// 值变更
const onChange = (select: string | Array<string>) => {
const value =
Object.prototype.toString.call(select) === '[object Array]'
? (select as Array<string>).join(',')
: select;
emit('change', value);
};
return { ns, c, curValue, onChange, 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
value={this.curValue}
allow-clear
class={this.ns.e('dropdown-list')}
multiple={this.c!.multiple}
placeholder={this.c!.placeHolder}
on-on-change={this.onChange}
>
{this.items.map(item => {
return <i-option value={item.value}>{item.text}</i-option>;
})}
</i-select>
</div>
)}
</div>
);
},
});
export default IBizDropDownList;
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 '@/styles/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;
console.log(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) {
console.log('error');
return;
}
// 请求成功,后台返回的是一个文件流
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 {
console.log('error');
}
})
.catch(error => {
console.error(error);
});
};
// 下载文件
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(
'upload',
{
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 default IBizFileUpload;
import { computed, defineComponent, ref, watch } from 'vue';
import type { PropType } from 'vue';
import { TextBoxEditorController } from '@ibiz-template/controller';
import { debounce } from 'lodash-es';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/editor/ibiz-input-box/ibiz-input-box.scss';
export default defineComponent({
props: {
value: [String, Number],
controller: {
type: TextBoxEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
disable: {
type: Boolean,
},
},
emits: {
change: (_value: string) => true,
},
setup(props, { emit }) {
const ns = useNamespace('input-box');
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'),
)}`}
>
<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.disable}
></i-input>
</div>
);
},
});
import { PickerEditorController } from '@ibiz-template/controller';
import { ref, watch, Ref, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/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>,
},
},
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) => {
console.log('query', query);
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}
>
{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}
></i-button>
) : null}
</div>
</div>
);
},
});
export default IBizMPicker;
import { PickerEditorController } from '@ibiz-template/controller';
import { ref, Ref, watch, computed, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/editor/ibiz-picker-dropdown/ibiz-picker-dropdown.scss';
import { debounce, isEmpty } from 'lodash-es';
export const IBizPickerDropdown = defineComponent({
name: 'IBizPickerDropdown',
props: {
value: {
type: String,
},
controller: {
type: PickerEditorController,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
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 (!isEmpty(newVal) && !isEmpty(value)) {
items.value.push({ [c.textName]: newVal, [c.keyName]: value });
}
}
},
{ immediate: true },
);
// 获取关联数据项值
const refValue = computed(() => {
if (c.valueItem && props.data) {
const key = props.data[c.valueItem];
if (key) {
return key;
}
return '';
}
return curValue;
});
// 下拉是否打开
const open = ref(false);
// 加载中
const loading: Ref<boolean> = ref(false);
// 往外抛值
const onACSelect = (item: IData) => {
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]);
}
};
// 搜索
const onSearch = async (query: string) => {
if (c.model.appDataEntity) {
// console.log('drop-down-query', query);
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 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 = () => {
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
loading={this.loading}
placeholder={this.c.placeHolder}
on-on-query-change={this.queryChange}
on-on-open-change={this.onSelectOpen}
on-on-change={this.onSelect}
on-on-clear={this.onClear}
>
{this.items.map((item, index) => {
return (
<i-option value={item[this.c.keyName]} key={index}>
{item[this.c.textName]}
</i-option>
);
})}
</i-select>
</div>
);
},
});
export default IBizPickerDropdown;
import { PickerEditorController } from '@ibiz-template/controller';
import { ref, watch, Ref, defineComponent, PropType } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/editor/ibiz-picker/ibiz-picker.scss';
import { isEmpty } from 'lodash-es';
export const IBizPicker = defineComponent({
name: 'IBizPicker',
props: {
value: {
type: String,
},
controller: {
type: PickerEditorController,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
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 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 openPickUpView = async () => {
const res = await c.openPickUpView(props.data);
if (res) {
handleOpenViewClose(res);
}
};
// 打开数据链接视图
const openLinkView = async () => {
const res = await c.openLinkView(props.data);
if (res) {
handleOpenViewClose(res);
}
};
// 往外抛值
const onACSelect = (item: IData) => {
console.log('onACSelect');
if (c.valueItem) {
emit('change', item[c.keyName], c.valueItem);
}
emit('change', item[c.textName]);
isShowAll.value = true;
};
// 搜索
const onSearch = async (query: string) => {
console.log('picker-auto-query', query);
if (c.model.appDataEntity) {
const res = await c.getServiceData(query, props.data);
if (res) {
items.value = res.data as IData[];
}
}
};
// 清除
const onClear = () => {
if (c.valueItem) {
emit('change', null, c.valueItem);
}
emit('change', null);
};
// 聚焦
const onFocus = (e: IData) => {
const query = isShowAll.value ? '' : e.target.value;
onSearch(query);
};
return {
ns,
c,
curValue,
items,
openPickUpView,
openLinkView,
onACSelect,
onSearch,
onClear,
onFocus,
};
},
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}
>
{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')}>
<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}
>
{this.items.map(item => {
return (
<div
class={this.ns.e('transfer-item')}
on-click={() => {
this.onACSelect(item);
}}
>
{item[this.c.textName]}
</div>
);
})}
</auto-complete>
<div class={this.ns.e('btns')}>
{this.c.pickupView ? (
<i-button
icon='ios-search'
on-click={this.openPickUpView}
></i-button>
) : null}
{this.c.linkView ? (
<i-button
icon='ios-search-outline'
on-click={this.openLinkView}
></i-button>
) : null}
</div>
</div>
)}
</div>
);
},
});
export default IBizPicker;
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 '@/styles/components/editor/ibiz-radio-button-list/ibiz-radio-button-list.scss';
export default defineComponent({
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-button-list');
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()}>
<radio-group
class={this.ns.b('radio-group')}
value={this.selectValue}
on-on-change={this.onSelectValueChange}
>
{this.items.map((_item, index: number) => (
<radio
key={index}
label={_item.value}
readonly={this.editorModel.readOnly}
>
<span class={this.ns.be('radio-group', 'text')}>
{_item.text}
</span>
</radio>
))}
</radio-group>
</div>
);
},
});
import { defineComponent } from 'vue';
import type { PropType } from 'vue';
import { SpanEditorController } from '@ibiz-template/controller';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/editor/ibiz-span/ibiz-span.scss';
export default defineComponent({
props: {
value: String,
controller: {
type: SpanEditorController,
required: true,
},
data: {
type: Object as PropType<IData>,
required: true,
},
},
setup() {
const ns = useNamespace('span');
return {
ns,
};
},
render() {
return <span class={this.ns.b()}>{this.value}</span>;
},
});
import { EditorModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent } from 'vue';
import '@/styles/components/editor/not-supported-editor/not-supported-editor.scss';
export default defineComponent({
props: {
modelData: {
type: EditorModel,
required: true,
},
},
setup() {
const ns = useNamespace('not-supported-editor');
return { ns };
},
render() {
return (
<div class={this.ns.b()}>
未支持的编辑器类型 - {this.modelData.editorType}
</div>
);
},
});
import { defineComponent, ref } from 'vue';
import { IndexViewModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { Icon, Layout } from 'view-design';
import '@/styles/components/layout/app-layout/app-layout.scss';
import { Namespace } from '@ibiz-template/core';
function renderLogo(ns: Namespace, model: IndexViewModel) {
return (
<div class={ns.e('logo')}>
{model.appIconPath ? (
<img class={ns.e('logo-img')} src={model.appIconPath}></img>
) : null}
<div class={ns.e('logo-caption')}>{model.caption}</div>
</div>
);
}
export const AppLayout = defineComponent({
name: 'AppLayout',
props: {
model: {
type: IndexViewModel,
required: true,
},
// 视图是否完成加载
isComplete: {
type: Boolean,
default: false,
},
},
setup(props, { emit }) {
const ns = useNamespace('layout');
// 菜单收缩变化
const collapseChange = ref(false);
const collapseMenus = () => {
collapseChange.value = !collapseChange.value;
emit('onCollapseChange', collapseChange.value);
};
const onBackClick = (event: MouseEvent) => {
event.stopPropagation();
emit('backClick');
};
return { ns, collapseChange, collapseMenus, onBackClick };
},
render() {
return this.isComplete ? (
<Layout
class={[this.ns.b(), this.ns.is('collapse', this.collapseChange)]}
>
<sider
hide-trigger
class={[this.ns.b('nav')]}
width={this.collapseChange ? 80 : 256}
value={this.collapseChange}
>
{renderLogo(this.ns, this.model)}
{this.$slots.menu}
</sider>
<layout class={[this.ns.b('content')]}>
<i-header class={this.ns.b('header')}>
<div class={this.ns.be('header', 'left')}>
{renderLogo(this.ns, this.model)}
<div class={this.ns.be('header', 'icon')}>
{!this.collapseChange ? (
<Icon
type='ios-arrow-back'
on-click={() => this.collapseMenus()}
/>
) : (
<Icon
type='ios-arrow-forward'
on-click={() => this.collapseMenus()}
/>
)}
</div>
<div
title='后退'
class={this.ns.be('header', 'back-icon')}
onClick={this.onBackClick}
>
<Icon type='ios-arrow-dropleft' />
</div>
{this.model.source.mainMenuAlign === 'TOP' ? (
<i-menu
class={this.ns.be('header', 'menu')}
mode='horizontal'
active-name='1'
></i-menu>
) : null}
</div>
<div class={this.ns.be('header', 'right')}>
<app-user />
</div>
</i-header>
<i-content class={this.ns.be('content', 'main')}>
{this.$slots.default}
</i-content>
{/* <i-footer class={this.ns.b('footer')}>Footer</i-footer> */}
</layout>
</Layout>
) : null;
},
});
import { defineComponent } from 'vue';
import { ControlModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/layout/control-layout/control-layout.scss';
/**
* 部件布局
*/
export default defineComponent({
props: {
modelData: ControlModel,
},
setup(props) {
const ns = useNamespace('control-layout');
const type = props.modelData?.source.controlType.toLowerCase();
return { ns, type };
},
render() {
return (
<div class={{ [this.ns.b()]: true, [this.ns.m(this.type)]: true }}>
{this.$slots.default}
</div>
);
},
});
import { AppLayout } from './app-layout/app-layout';
import ControlLayout from './control-layout/control-layout';
import { ViewBase } from './view-base/view-base';
import { ViewLayout } from './view-layout/view-layout';
export { AppLayout, ControlLayout, ViewLayout, ViewBase };
import { MDViewController, ViewController } from '@ibiz-template/controller';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, PropType } from 'vue';
export const ViewBase = defineComponent({
props: {
controller: {
type: Object as PropType<ViewController>,
required: true,
},
},
setup() {
const ns = useNamespace('view');
return { ns };
},
render() {
const c = this.controller;
// 外面的插槽同样传给view-layout
const inheritSlots: IData = {};
Object.keys(this.$scopedSlots).forEach(key => {
if (key === 'default') {
inheritSlots[key] = (arg: IData) => [
c.viewLoading.isLoading && <spin size='large' fix></spin>,
this.$scopedSlots[key]!(arg),
];
} else {
inheritSlots[key] = (arg: IData) => this.$scopedSlots[key]!(arg);
}
});
return (
<view-layout
class={[
this.ns.b(),
c.complete && this.ns.b(this.controller.model.typeClass),
]}
isComplete={c.complete}
modelData={c.model}
viewMode={c.modal.mode}
scopedSlots={{
caption: () => {
if (c.complete && c.model.source.getPSSysImage()) {
return [
<app-icon icon={c.model.source.getPSSysImage()}></app-icon>,
c.caption,
];
}
return c.caption;
},
toolbar: () => {
if (c.complete && c.model.toolbar) {
return [
<view-toolbar
modelData={c.model.toolbar}
on-neuronInit={c.nerve.onNeuronInit(
c.model.toolbar.source.name,
)}
viewMode={c.modal.mode}
></view-toolbar>,
];
}
return null;
},
quickSearch: () => {
const _c = c as MDViewController;
if (_c.complete && _c.model.source.enableQuickSearch) {
return (
<quick-search
value={_c.query}
viewMode={c.modal.mode}
placeholder={_c.model.placeholder}
on-update={(val: string) => {
_c.query = val;
}}
on-search={() => _c.onSearch()}
></quick-search>
);
}
},
...inheritSlots,
}}
/>
);
},
});
import { computed, defineComponent } from 'vue';
import { ViewModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/layout/view-layout/view-layout.scss';
export const ViewLayout = defineComponent({
name: 'ViewLayout',
props: {
modelData: {
type: ViewModel,
required: true,
},
viewMode: {
type: String, // ViewMode 类型
default: 'ROUTE',
},
// 视图是否完成加载
isComplete: {
type: Boolean,
default: false,
},
isLoading: {
type: Boolean,
default: false,
},
},
setup(props) {
const ns = useNamespace('view-layout');
// 是否显示头部
const isShowHeader = computed(() => {
return props.modelData.source.showCaptionBar || !!props.modelData.toolbar;
});
return { ns, isShowHeader };
},
render() {
return !this.isComplete ? (
<div class={[this.ns.b(), this.ns.m(this.viewMode.toLowerCase())]}>
{this.isLoading ? <spin size='large' fix></spin> : null}
</div>
) : (
<div
class={[
this.ns.b(),
this.ns.m(this.modelData.modelClass),
this.ns.m(this.viewMode.toLowerCase()),
this.ns.is('no-header', !this.isShowHeader),
this.ns.is('loading', this.isLoading),
]}
>
{this.isLoading ? <spin size='large' fix></spin> : null}
{this.isShowHeader ? (
<div class={this.ns.b('header')}>
<div class={this.ns.b('header-content')}>
<div class={this.ns.be('header-content', 'left')}>
{this.modelData.source.showCaptionBar ? (
<div class={this.ns.be('header-content', 'caption')}>
{this.$scopedSlots.caption && this.$scopedSlots.caption({})}
</div>
) : null}
</div>
<div class={this.ns.be('header-content', 'right')}>
<div class={this.ns.e('quick-search')}>
{this.$scopedSlots.quickSearch &&
this.$scopedSlots.quickSearch({})}
</div>
<div class={this.ns.e('toolbar')}>
{this.$scopedSlots.toolbar && this.$scopedSlots.toolbar({})}
</div>
</div>
</div>
<div class={this.ns.b('header-exp')}></div>
</div>
) : null}
<div class={this.ns.b('top')}>
<div class={this.ns.be('top', 'message')}></div>
<div class={this.ns.be('top', 'search-form')}></div>
</div>
<div class={this.ns.b('content')}>
<div class={this.ns.be('content', 'left')}></div>
<div class={this.ns.be('content', 'body')}>
{this.$scopedSlots.default && this.$scopedSlots.default({})}
</div>
<div class={this.ns.be('content', 'right')}></div>
</div>
<div class={this.ns.b('footer')}>
{this.$scopedSlots.footer && this.$scopedSlots.footer({})}
</div>
</div>
);
},
});
import { defineComponent, getCurrentInstance, ref } from 'vue';
import { ModelUtil } from '@ibiz-template/model';
import {
IRouteViewData,
parseRouteViewData,
useRoute,
} from '@ibiz-template/vue-util';
import { Route } from 'vue-router';
import { getViewComponentName } from '@/util';
export default defineComponent({
name: 'RouterShell',
props: {
level: {
type: Number,
default: 0,
},
},
setup(props, ctx) {
const { proxy } = getCurrentInstance()!;
const route = useRoute(proxy) as Route;
const viewData = ref<IRouteViewData>({});
const isLoaded = ref(false);
const viewComponentName = ref('');
// 根据应用模型解析视图参数
ModelUtil.getModelService().then(service => {
const appModel = service.app;
if (appModel) {
// 获取视图
viewData.value = parseRouteViewData(appModel, route, props.level);
// 确定视图组件
viewComponentName.value = getViewComponentName(
viewData.value.viewType!,
);
isLoaded.value = true;
ctx.emit('viewFound', { modelPath: viewData.value.viewPath });
}
});
// 视图初始化事件,往上抛
const onNeuronInit = (...args: IData[]) => {
ctx.emit('neuronInit', ...args);
};
return {
route,
viewData,
isLoaded,
viewComponentName,
onNeuronInit,
};
},
render(h) {
if (!this.isLoaded) {
return null;
}
return h('AppTransition', {}, [
h(this.viewComponentName, {
props: {
context: this.viewData.context,
params: this.viewData.params,
modelPath: this.viewData.viewPath,
},
on: {
neuronInit: this.onNeuronInit,
},
}),
]);
},
});
import { IModal } from '@ibiz-template/runtime';
import { useEditViewController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
export const EditView = defineComponent({
props: {
context: Object as PropType<IContext>,
params: { type: Object as PropType<IParams> },
modelPath: { type: String, required: true },
modal: { type: Object as PropType<IModal> },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = useEditViewController(proxy, props.modelPath);
return { c };
},
render() {
return (
<view-base controller={this.c}>
{this.c.complete && (
<edit-form-control
modelData={this.c.model.form}
context={this.c.context}
params={this.c.params}
on-neuronInit={this.c.nerve.onNeuronInit(
this.c.model.form.source.name,
)}
></edit-form-control>
)}
</view-base>
);
},
});
import { IModal } from '@ibiz-template/runtime';
import { useGridViewController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
export const GridView = defineComponent({
props: {
context: Object as PropType<IContext>,
params: { type: Object as PropType<IParams>, default: () => ({}) },
modelPath: { type: String, required: true },
modal: { type: Object as PropType<IModal> },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = useGridViewController(proxy, props.modelPath);
return { c };
},
render() {
return (
<view-base controller={this.c}>
{this.c.complete && (
<grid-control
modelData={this.c.model.grid}
context={this.c.context}
params={this.c.params}
on-neuronInit={this.c.nerve.onNeuronInit(
this.c.model.grid.source.name,
)}
grid-row-active-mode={this.c.model.gridRowActiveMode}
></grid-control>
)}
</view-base>
);
},
});
import { GridView } from './grid-view/grid-view';
import { EditView } from './edit-view/edit-view';
import { PickupGridView } from './pickup-grid-view/pickup-grid-view';
import { PickupView } from './pickup-view/pickup-view';
import { MPickupView } from './mpickup-view/mpickup-view';
export * from './opt-view/opt-view';
export { GridView, EditView, PickupGridView, PickupView, MPickupView };
import {
useMPickupViewController,
useNamespace,
} from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType, Ref, ref } from 'vue';
import '@/styles/components/views/mpickup-view/mpickup-view.scss';
import { IModal } from '@ibiz-template/runtime';
export const MPickupView = defineComponent({
props: {
context: Object as PropType<IContext>,
params: { type: Object as PropType<IParams>, default: () => ({}) },
modelPath: { type: String, required: true },
modal: { type: Object as PropType<IModal> },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = useMPickupViewController(proxy, props.modelPath);
// model.typeClass
const ns = useNamespace('view-dempickupview');
// UI层单击选中的数组
const UISelections: Ref<IData[]> = ref([]);
const handleSelectionClick = (selection: IData) => {
const index = UISelections.value.indexOf(selection);
if (index === -1) {
UISelections.value.push(selection);
} else {
UISelections.value.splice(index, 1);
}
};
const isSelected = (selection: IData) => {
return UISelections.value.includes(selection);
};
const addRight = () => {
c.addSelections(c.embedSelection);
UISelections.value = [];
};
const addRightAll = () => {
c.selectAll();
UISelections.value = [];
};
const removeRight = () => {
c.removeSelections(UISelections.value);
UISelections.value = [];
};
const removeRightAll = () => {
c.selfSelection = [];
UISelections.value = [];
};
return {
ns,
c,
addRight,
addRightAll,
removeRight,
removeRightAll,
handleSelectionClick,
isSelected,
};
},
render() {
return (
<view-base
class={this.ns.b()}
controller={this.c}
scopedSlots={{
footer: () => {
return (
<div class={this.ns.b('footer')}>
<i-button
on-click={() => {
this.c.onOkButtonClick();
}}
>
确定
</i-button>
<i-button
on-click={() => {
this.c.onCancelButtonClick();
}}
>
取消
</i-button>
</div>
);
},
}}
>
<div class={this.ns.b('content')}>
{this.c.complete && this.c.model.pickupViewPanel && (
<div class={this.ns.b('left')}>
<pickup-view-panel
modelData={this.c.model.pickupViewPanel}
context={this.c.context}
params={this.c.params}
on-neuronInit={this.c.nerve.onNeuronInit(
this.c.model.pickupViewPanel.source.name,
)}
></pickup-view-panel>
</div>
)}
<div class={this.ns.b('center')}>
<i-button on-click={this.addRight} title='右移'>
{'>'}
</i-button>
<i-button on-click={this.removeRight} title='左移'>
{'<'}
</i-button>
<i-button on-click={this.addRightAll} title='全部右移'>
{'>>'}
</i-button>
<i-button on-click={this.removeRightAll} title='全部左移'>
{'<<'}
</i-button>
</div>
<div class={this.ns.b('right')}>
{this.c.selfSelection.map(item => {
return (
<div
key={item.srfkey}
class={[
this.ns.be('right', 'list-item'),
this.ns.is('selected', this.isSelected(item)),
]}
on-click={() => this.handleSelectionClick(item)}
>
{item.srfmajortext}
<icon
type='ios-close'
size={24}
on-click={() => {
this.c.removeSelections([item]);
}}
/>
</div>
);
})}
</div>
</div>
</view-base>
);
},
});
import { IModal } from '@ibiz-template/runtime';
import { useNamespace, useOptViewController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
import '@/styles/components/views/opt-view/opt-view.scss';
export const OptView = defineComponent({
props: {
context: Object as PropType<IContext>,
params: { type: Object as PropType<IParams> },
modelPath: { type: String, required: true },
modal: { type: Object as PropType<IModal> },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = useOptViewController(proxy, props.modelPath);
const ns = useNamespace('view-deoptview');
return { c, ns };
},
render() {
return (
<view-base
class={this.ns.b()}
controller={this.c}
scopedSlots={{
footer: () => {
return (
<div class={this.ns.b('footer')}>
<i-button
on-click={() => {
this.c.onOkButtonClick();
}}
>
确定
</i-button>
<i-button
on-click={() => {
this.c.onCancelButtonClick();
}}
>
取消
</i-button>
</div>
);
},
}}
>
{this.c.complete && (
<edit-form-control
modelData={this.c.model.form}
context={this.c.context}
params={this.c.params}
on-neuronInit={this.c.nerve.onNeuronInit(
this.c.model.form.source.name,
)}
></edit-form-control>
)}
</view-base>
);
},
});
import { IModal } from '@ibiz-template/runtime';
import { usePickupGridViewController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
export const PickupGridView = defineComponent({
props: {
context: Object as PropType<IContext>,
params: { type: Object as PropType<IParams>, default: () => ({}) },
modelPath: { type: String, required: true },
modal: { type: Object as PropType<IModal> },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = usePickupGridViewController(proxy, props.modelPath);
return { c };
},
render() {
return (
<view-base controller={this.c}>
{this.c.complete && (
<grid-control
modelData={this.c.model.grid}
context={this.c.context}
params={this.c.params}
on-neuronInit={this.c.nerve.onNeuronInit(
this.c.model.grid.source.name,
)}
grid-row-active-mode={this.c.model.gridRowActiveMode}
></grid-control>
)}
</view-base>
);
},
});
import { IModal } from '@ibiz-template/runtime';
import { usePickupViewController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
export const PickupView = defineComponent({
props: {
context: Object as PropType<IContext>,
params: { type: Object as PropType<IParams>, default: () => ({}) },
modelPath: { type: String, required: true },
modal: { type: Object as PropType<IModal> },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = usePickupViewController(proxy, props.modelPath);
return { c };
},
render() {
return (
<view-base controller={this.c}>
{this.c.complete && this.c.model.pickupViewPanel && (
<pickup-view-panel
modelData={this.c.model.pickupViewPanel}
context={this.c.context}
params={this.c.params}
on-neuronInit={this.c.nerve.onNeuronInit(
this.c.model.pickupViewPanel.source.name,
)}
></pickup-view-panel>
)}
</view-base>
);
},
});
import { IBizContext, Namespace } from '@ibiz-template/core';
import {
AppMenuModel,
AppMenuItemModel,
IPSAppMenuItem,
} from '@ibiz-template/model';
// import { AppFuncCommand } from '@ibiz-template/runtime';
import { useAppMenuController, useNamespace } from '@ibiz-template/vue-util';
import {
Dropdown,
DropdownItem,
DropdownMenu,
Icon,
MenuItem,
Tooltip,
} from 'view-design';
import {
defineComponent,
getCurrentInstance,
onMounted,
Ref,
ref,
watch,
} from 'vue';
import '@/styles/components/widgets/app-menu/app-menu.scss';
/**
* 递归生成菜单数据,递给 antd 的 Menu 组件
*
* @author chitanda
* @date 2022-07-25 10:07:28
* @param {AppMenuItemModel[]} items
* @return {*} {any[]}
*/
function getMenus(items: AppMenuItemModel[]): IData[] {
return items.map(item => {
const data: IData = {
key: item.source.id,
label: item.label,
image: item.image,
};
if (item.children) {
data.children = getMenus(item.children);
}
return data;
});
}
/**
* 绘制菜单项
* @author lxm
* @date 2022-08-16 14:08:20
* @param {IData} menu
* @returns {*}
*/
function renderMenuItem(menu: IData, collapseChange: boolean, ns: Namespace) {
return !collapseChange ? (
<MenuItem class={ns.e('item')} name={menu.key}>
<app-icon class={ns.e('icon')} icon={menu.image}></app-icon>
{menu.label}
</MenuItem>
) : (
<Tooltip
class={ns.b('tooltip')}
content={menu.label}
placement={'left'}
theme='light'
>
<MenuItem class={ns.e('item')} name={menu.key}>
<app-icon class={ns.e('icon')} icon={menu.image}></app-icon>
{!menu.image ? menu.label.slice(0, 1) : null}
</MenuItem>
</Tooltip>
);
}
/**
* 绘制收缩分组菜单项
* @param {IData} menu
* @return {*}
* @author: zhujiamin
* @Date: 2022-09-08 16:39:15
*/
function renderDropDownMenuItem(
menu: IData,
collapseChange: boolean,
ns: Namespace,
) {
return (
<DropdownItem class={ns.be('submenu', 'item')} name={menu.key}>
<MenuItem name={menu.key}>{menu.label}</MenuItem>
</DropdownItem>
);
}
/**
* 绘制子菜单
* @author lxm
* @date 2022-08-16 14:08:29
* @param {IData} subMenu
* @returns {*}
*/
function renderSubmenu(
isFirst: boolean,
subMenu: IData,
collapseChange: boolean,
ns: Namespace,
) {
return !collapseChange ? (
<submenu name={subMenu.key}>
<template slot='title'>
<app-icon class={ns.e('icon')} icon={subMenu.image}></app-icon>
{subMenu.label}
</template>
{subMenu.children.map((item: IData) => {
if (item.children) {
return renderSubmenu(false, item, collapseChange, ns);
}
return renderMenuItem(item, collapseChange, ns);
})}
</submenu>
) : (
<Dropdown placement='left' class={ns.b('submenu')}>
<div class={ns.be('submenu', 'title')}>
{isFirst
? [
<app-icon class={ns.e('icon')} icon={subMenu.image}></app-icon>,
!subMenu.image ? subMenu.label.slice(0, 1) : null,
]
: [
<app-icon class={ns.e('icon')} icon={subMenu.image}></app-icon>,
subMenu.label,
]}
{isFirst ? null : <Icon type='ios-arrow-forward' />}
</div>
<DropdownMenu class={ns.be('submenu', 'list')} slot='list'>
{subMenu.children.map((item: IData) => {
if (item.children) {
return renderSubmenu(false, item, collapseChange, ns);
}
return renderDropDownMenuItem(item, collapseChange, ns);
})}
</DropdownMenu>
</Dropdown>
);
}
export const AppMenu = defineComponent({
name: 'AppMenu',
props: {
modelData: AppMenuModel,
context: IBizContext,
collapseChange: Boolean,
currentPath: String,
},
setup(props, { emit }) {
const { proxy } = getCurrentInstance()!;
const c = useAppMenuController(proxy, props.modelData!, props.context!, {});
const menus = ref<IData[]>([]);
// 默认激活菜单项
const defaultActive = ref('');
// 默认展开菜单项数组
const defaultOpens: Ref<string[]> = ref([]);
// 路由对象
const route = proxy.$route;
c.hooks.complete.tap(() => {
menus.value = getMenus(c.model.items);
});
let menuClickDoing = false;
const onClick = async (key: string) => {
menuClickDoing = true;
await c.onClickMenuItem(key);
menuClickDoing = false;
};
const ns = useNamespace('app-menu');
// 手动更新iView菜单
const updateMenu = () => {
setTimeout(() => {
if (proxy.$refs.menu) {
const menu: IData = proxy.$refs.menu;
menu.updateActiveName();
menu.updateOpened();
}
}, 500);
};
// 菜单选中回显,监听视图传进来的currentPath
watch(
() => props.currentPath,
(newVal, oldVal) => {
// 新旧值不一样,且新值不为空时变更
if (newVal !== oldVal && newVal) {
if (menuClickDoing === true) {
emit('menuRouteChange');
}
const findItem = c.model.allItems.find(item => {
return item.viewModelPath === newVal;
});
if (findItem) {
defaultActive.value = findItem.source.id;
updateMenu();
}
}
},
{ deep: true, immediate: true },
);
// 处理默认展开父菜单
const handleDefaultOpen = (parent: IPSAppMenuItem) => {
if (parent && parent.itemType === 'MENUITEM') {
const findIndex = defaultOpens.value.findIndex(open => {
return open === parent.id;
});
if (findIndex === -1) {
defaultOpens.value.push(parent.id);
}
const gParent = parent.getParentPSModelObject() as IPSAppMenuItem;
if (gParent) {
handleDefaultOpen(gParent);
}
}
};
// 回显完成后,遍历展开父菜单
watch(
() => defaultActive.value,
newVal => {
const findItem = c.model.allItems.find(item => {
return newVal === item.id;
});
if (findItem) {
const parent =
findItem.source.getParentPSModelObject() as IPSAppMenuItem;
// 遍历取父菜单项
handleDefaultOpen(parent);
updateMenu();
}
},
);
onMounted(() => {
// 默认激活的菜单项
const defaultActiveMenuItem = c.model.allItems.find(item => {
return item.source.openDefault && !item.source.hidden;
});
if (defaultActiveMenuItem && !route.params.view2) {
defaultActive.value = defaultActiveMenuItem.id;
onClick(defaultActive.value);
}
// 默认展开的菜单项数组
const defaultOpensArr = c.model.allItems.filter(item => {
return item.source.expanded && !item.source.hidden;
});
if (defaultOpensArr.length > 0) {
defaultOpensArr.forEach(item => {
defaultOpens.value.push(item.id);
});
}
updateMenu();
});
return { menus, onClick, ns, defaultActive, defaultOpens };
},
render() {
return (
<i-menu
ref={'menu'}
active-name={this.defaultActive}
open-names={this.defaultOpens}
on-on-select={this.onClick}
theme='light'
width='auto'
class={[this.ns.b(), this.ns.is('collapse', this.collapseChange)]}
>
{this.menus.map(item => {
if (item.children?.length > 0) {
return renderSubmenu(true, item, this.collapseChange, this.ns);
}
return renderMenuItem(item, this.collapseChange, this.ns);
})}
</i-menu>
);
},
});
import { EditFormController } from '@ibiz-template/controller';
import { IBizContext } from '@ibiz-template/core';
import { EditFormModel } from '@ibiz-template/model';
import { useEditFormController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType, reactive } from 'vue';
export const EditFormControl = defineComponent({
name: 'EditFormControl',
props: {
modelData: {
type: EditFormModel,
required: true,
},
context: {
type: IBizContext,
required: true,
},
params: { type: Object as PropType<IParams>, default: () => ({}) },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = useEditFormController(
proxy,
props.modelData,
props.context,
props.params,
);
// reactive后子组件才能监控和响应式更新
const reactiveC = reactive(c) as EditFormController;
return { c, reactiveC };
},
render() {
return !this.c.complete ? null : (
<form-control
model-data={this.modelData}
context={this.context}
controller={this.reactiveC}
></form-control>
);
},
});
export default EditFormControl;
import { FormButtonController } from '@ibiz-template/controller';
import { FormButtonModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent } from 'vue';
import '@/styles/components/widgets/form/form-button.scss';
export const FormButton = defineComponent({
name: 'FormButton',
props: {
modelData: {
type: FormButtonModel,
required: true,
},
controller: {
type: FormButtonController,
required: true,
},
},
setup() {
const ns = useNamespace('form-button');
return { ns };
},
render() {
if (!this.controller.visible) {
return null;
}
return (
<i-button class={this.ns.b()}>{this.modelData.source.caption}</i-button>
);
},
});
export default FormButton;
import { FormController } from '@ibiz-template/controller';
import { IBizContext } from '@ibiz-template/core';
import {
FormDetailModel,
FormGroupPanelModel,
FormTabPanelModel,
FormTabPageModel,
FormModel,
FormItemModel,
} from '@ibiz-template/model';
import { useForceTogether, useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, toRef, computed } from 'vue';
import '@/styles/components/widgets/form/form.scss';
/**
* 根据类型绘制表单成员
*
* @author lxm
* @date 2022-08-23 16:08:31
* @param {FormDetailModel} detail 成员模型
* @param {FormController} controller 表单控制器
* @returns {*}
*/
function renderByDetailType(
detail: FormDetailModel,
controller: FormController,
) {
const commonProps = {
modelData: detail,
controller: computed(() => controller.details[detail.source.name]),
};
switch (detail.source.detailType) {
// 分页部件
case 'TABPANEL':
return (
<form-tab-panel key={detail.id} props={{ ...commonProps }}>
{(detail as FormTabPanelModel).children.map(page => {
return (
<form-tab-page
key={page.id}
props={{
modelData: page,
controller: controller.details[page.source.name],
caption: page.source.caption,
}}
>
{(page as FormTabPageModel).children.map(item =>
renderByDetailType(item, controller),
)}
</form-tab-page>
);
})}
</form-tab-panel>
);
// 分组面板
case 'GROUPPANEL':
return (
<form-group-panel key={detail.id} props={{ ...commonProps }}>
{(detail as FormGroupPanelModel).children.map(item =>
renderByDetailType(item, controller),
)}
</form-group-panel>
);
// 表单项
case 'FORMITEM':
if ((detail as FormItemModel).source.hidden) {
return null;
}
return (
<app-form-item
key={detail.id}
props={{ ...commonProps }}
></app-form-item>
);
// 表单按钮
case 'BUTTON':
return (
<form-button key={detail.id} props={{ ...commonProps }}></form-button>
);
// 关系界面
case 'DRUIPART':
return (
<form-druipart
key={detail.id}
props={{ ...commonProps }}
></form-druipart>
);
// 直接内容
case 'RAWITEM':
return (
<form-raw-item
key={detail.id}
props={{ ...commonProps }}
></form-raw-item>
);
default:
return <div>暂未支持的表单项类型: {detail.source.detailType}</div>;
}
}
export const FormControl = defineComponent({
name: 'FormControl',
props: {
modelData: {
type: FormModel,
required: true,
},
context: IBizContext,
controller: {
type: FormController,
required: true,
},
},
setup(props) {
const { proxy } = getCurrentInstance()!;
useForceTogether(proxy, props.controller);
const ns = useNamespace('form');
const c = toRef(props, 'controller');
return { ns, c };
},
render(_h) {
return (
<control-layout modelData={this.c.model}>
{this.c.complete && (
<div class={this.ns.b()}>
<form-page modelData={this.c.model}>
{this.$props.modelData.children.map(page => {
return (
<form-page-item
key={page.id}
caption={page.source.caption}
model-data={page}
controller={this.c.details[page.source.name]}
>
{page.children.map(detail =>
renderByDetailType(detail, this.c),
)}
</form-page-item>
);
})}
</form-page>
</div>
)}
</control-layout>
);
},
});
import { FormDRUIPartController, ViewNeuron } from '@ibiz-template/controller';
import { FormDRUIPartModel } from '@ibiz-template/model';
import { defineComponent, getCurrentInstance } from 'vue';
import { IModal, ViewMode } from '@ibiz-template/runtime';
import { useController, useNamespace } from '@ibiz-template/vue-util';
import { getViewComponentName } from '@/util';
import '@/styles/components/widgets/form/form-druipart.scss';
export const FormDRUIPart = defineComponent({
name: 'FormDRUIPart',
props: {
modelData: {
type: FormDRUIPartModel,
required: true,
},
controller: {
type: FormDRUIPartController,
required: true,
},
},
setup(props) {
const ns = useNamespace('form-druipart');
// 视图组件名称和视图模型路径
const viewComponentName = getViewComponentName(
props.modelData.embedView.source.viewType,
);
const viewPath = props.modelData.embedView.source.modelPath;
// 模态对象
const modal: IModal = { mode: ViewMode.EMBED };
// 绑定强制刷新
const { proxy } = getCurrentInstance()!;
useController(proxy, props.controller);
const onNeuronInit = (n: ViewNeuron) => {
props.controller.setViewNeuron(n);
};
return {
ns,
modal,
viewComponentName,
viewPath,
onNeuronInit,
};
},
render(h) {
if (!this.controller.visible || !this.controller.viewComponentKey) {
return null;
}
return (
<div
v-show={this.controller.visible}
layout-pos={this.modelData.source.getPSLayoutPos()}
class={this.ns.b()}
>
{h(this.viewComponentName, {
props: {
context: this.$props.controller.context,
params: this.$props.controller.params,
modal: this.modal,
modelPath: this.viewPath,
},
key: this.controller.viewComponentKey,
on: {
neuronInit: this.onNeuronInit,
},
})}
{this.controller.showMask ? (
<div class={this.ns.e('mask')}>请先保存主数据</div>
) : null}
</div>
);
},
});
export default FormDRUIPart;
import { FormGroupPanelController } from '@ibiz-template/controller';
import { FormGroupPanelModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, ref, VNode } from 'vue';
import '@/styles/components/widgets/form/form-group.scss';
export const FormGroupPanel = defineComponent({
name: 'FormGroupPanel',
props: {
modelData: {
type: FormGroupPanelModel,
required: true,
},
controller: {
type: FormGroupPanelController,
required: true,
},
},
setup(props) {
const ns = useNamespace('form-group');
const isCollapse = ref(
props.modelData.defaultExpansion || props.modelData.disableClose,
);
const changeCollapse = () => {
isCollapse.value = !isCollapse.value;
};
return { ns, isCollapse, changeCollapse };
},
render() {
if (!this.controller.visible) {
return null;
}
const defaultSlots: VNode[] = this.$slots.default || [];
const content = (
<app-row slot='content' layout={this.modelData.source.getPSLayout()}>
{defaultSlots.map(slot => {
const opts = slot.componentOptions!;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (opts.propsData || {}) as Record<string, any>;
return (
<app-col
layoutPos={props.modelData.source.getPSLayoutPos()}
modelData={props.modelData}
>
{slot}
</app-col>
);
})}
</app-row>
);
const classArr: string[] = [
this.ns.b(),
this.ns.m(this.modelData.modelClass),
];
if (this.modelData.showHeader === true) {
classArr.push(this.ns.m('show-header'));
classArr.push(this.ns.b('collapse'));
if (this.modelData.disableClose) {
classArr.push(this.ns.bm('collapse', 'disable-close'));
classArr.push(this.ns.is('collapse', true));
} else {
classArr.push(this.ns.is('collapse', this.isCollapse));
}
}
let header: unknown = null;
if (this.modelData.showHeader) {
header = (
<div class={[this.ns.b('header')]} onClick={this.changeCollapse}>
<div class={[this.ns.be('header', 'left')]}>
<div class={[this.ns.e('caption')]}>
{this.modelData.source.caption}
</div>
</div>
<div class={[this.ns.be('header', 'right')]}>
<div class={[this.ns.e('toolbar')]}>{this.$slots.toolbar}</div>
</div>
</div>
);
}
return (
<div class={classArr}>
{header}
<div class={[this.ns.b('content')]}>{content}</div>
</div>
);
},
});
export default FormGroupPanel;
import { useNamespace } from '@ibiz-template/vue-util';
import { computed, defineComponent } from 'vue';
import '@/styles/components/widgets/form/form-item-container.scss';
export default defineComponent({
props: {
required: {
type: Boolean,
required: true,
},
error: {
type: String,
},
label: {
type: String,
required: true,
},
labelPos: {
type: String,
required: true,
},
labelWidth: {
type: Number,
required: true,
},
},
setup(props) {
const ns = useNamespace('form-item-container');
const cssVars = computed(() => {
if (props.labelWidth !== 130) {
return ns.cssVarBlock({ 'label-width': `${props.labelWidth}px` });
}
return {};
});
return { ns, cssVars };
},
render() {
return (
<div
class={[
this.ns.b(),
this.ns.m(this.labelPos.toLowerCase()),
this.ns.is('required', this.required),
this.ns.is('error', !!this.error),
]}
style={this.cssVars}
>
<div class={[this.ns.b('content')]}>
{this.labelPos === 'LEFT' || this.labelPos === 'TOP' ? (
<div class={[this.ns.e('label')]} title={this.label}>
{this.label}
</div>
) : null}
<div class={[this.ns.e('editor')]}>{this.$slots.default}</div>
{this.labelPos === 'RIGHT' || this.labelPos === 'BOTTOM' ? (
<div class={[this.ns.e('label')]} title={this.label}>
{this.label}
</div>
) : null}
</div>
{this.error ? (
<div class={[this.ns.b('error')]}>{this.error}</div>
) : null}
</div>
);
},
});
import { FormItemController } from '@ibiz-template/controller';
import { FormItemModel } from '@ibiz-template/model';
import { defineComponent, computed, reactive } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/widgets/form/form-item.scss';
export const FormItem = defineComponent({
name: 'FormItem',
props: {
modelData: {
type: FormItemModel,
required: true,
},
controller: {
type: FormItemController,
required: true,
},
},
setup(props) {
const ns = useNamespace('form-item');
const c = reactive(props.controller);
const value = computed(() => props.controller.value);
const onValueChange = (val: unknown, name?: string) => {
props.controller.setDataValue(val, name);
};
return { ns, c, value, onValueChange };
},
render(h) {
if (!this.c.visible) {
return null;
}
return (
<form-item-container
class={[this.ns.b(), this.ns.m(this.modelData.modelClass)]}
required={this.c.required}
error={this.c.error}
label={this.modelData.source.caption}
label-pos={this.c.model.labelPos}
label-width={this.c.model.labelWidth}
>
{this.controller.editorProvider ? (
h(this.controller.editorProvider.formEditor, {
props: {
value: this.value,
data: this.controller.data,
controller: this.controller.editor,
disable: this.controller.disabled,
},
on: {
change: this.onValueChange,
},
})
) : (
<not-supported-editor modelData={this.modelData.editor} />
)}
</form-item-container>
);
},
});
export default FormItem;
import { FormPageController } from '@ibiz-template/controller';
import { FormPageModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, VNode } from 'vue';
export default defineComponent({
props: {
modelData: {
type: FormPageModel,
required: true,
},
controller: {
type: FormPageController,
required: true,
},
caption: {
type: String,
required: true,
},
},
setup() {
const ns = useNamespace('form-page-item');
return { ns };
},
render() {
const defaultSlots: VNode[] = this.$slots.default || [];
defaultSlots.forEach(item => {
const data = item.data;
if (data) {
data.class = { [this.ns.b('child')]: true };
}
});
return (
<app-row
class={[this.ns.b(), this.ns.m(this.modelData.modelClass)]}
layout={this.modelData.source.getPSLayout()}
>
{defaultSlots.map(slot => {
const opts = slot.componentOptions!;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (opts.propsData || {}) as Record<string, any>;
return (
<app-col
layoutPos={props.modelData.source.getPSLayoutPos()}
modelData={props.modelData}
>
{slot}
</app-col>
);
})}
</app-row>
);
},
});
import { defineComponent, ref, VNode } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import { FormModel } from '@ibiz-template/model';
import '@/styles/components/widgets/form/form-page.scss';
export const FormPage = defineComponent({
name: 'FormPage',
props: {
modelData: {
type: FormModel,
required: true,
},
},
setup(props) {
const ns = useNamespace('form-page');
// tabs标识,嵌套时区分用
const tabName = ref(props.modelData.source.name);
return { ns, tabName };
},
render() {
const defaultSlots: VNode[] = this.$slots.default || [];
if (defaultSlots.length === 1) {
return <div class={this.ns.b()}>{defaultSlots}</div>;
}
return (
<tabs class={[this.ns.b(), this.ns.b('tab')]} name={this.tabName}>
{defaultSlots.map(slot => {
const opts = slot.componentOptions!;
const props = (opts.propsData || {}) as Record<string, unknown>;
return (
<tab-pane
class={this.ns.b('tab-item')}
tab={this.tabName}
label={props.caption}
name={slot.key}
>
{slot}
</tab-pane>
);
})}
</tabs>
);
},
});
export default FormPage;
import { FormRawItemController } from '@ibiz-template/controller';
import { FormRawItemModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent } from 'vue';
import '@/styles/components/widgets/form/form-raw-item.scss';
export const FormRawItem = defineComponent({
name: 'FormRawItem',
props: {
modelData: {
type: FormRawItemModel,
required: true,
},
controller: {
type: FormRawItemController,
required: true,
},
},
setup() {
const ns = useNamespace('form-raw-item');
return { ns };
},
render() {
if (!this.controller.visible) {
return null;
}
return <div class={this.ns.b()}>{this.modelData.source.rawContent}</div>;
},
});
export default FormRawItem;
import { FormTabPageController } from '@ibiz-template/controller';
import { FormTabPageModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, VNode } from 'vue';
export default defineComponent({
props: {
modelData: {
type: FormTabPageModel,
required: true,
},
controller: {
type: FormTabPageController,
required: true,
},
caption: {
type: String,
required: true,
},
},
setup() {
const ns = useNamespace('form-tab-page');
return { ns };
},
render() {
const defaultSlots: VNode[] = this.$slots.default || [];
defaultSlots.forEach(item => {
const data = item.data;
if (data) {
data.class = { [this.ns.b('child')]: true };
}
});
return (
<app-row
class={[this.ns.b(), this.ns.m(this.modelData.modelClass)]}
layout={this.modelData.source.getPSLayout()}
>
{defaultSlots.map(slot => {
const opts = slot.componentOptions!;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (opts.propsData || {}) as Record<string, any>;
return (
<app-col
layoutPos={props.modelData.source.getPSLayoutPos()}
modelData={props.modelData}
>
{slot}
</app-col>
);
})}
</app-row>
);
},
});
import { FormTabPanelModel } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent, ref, VNode } from 'vue';
export default defineComponent({
name: 'FormTabPanel',
props: {
modelData: {
type: FormTabPanelModel,
required: true,
},
},
setup(props) {
const ns = useNamespace('form-tab-panel');
// tabs标识,嵌套时区分用
const tabName = ref(props.modelData.source.name);
return { ns, tabName };
},
render() {
const defaultSlots: VNode[] = this.$slots.default || [];
return (
<tabs class={[this.ns.b()]} name={this.tabName}>
{defaultSlots.map(slot => {
const opts = slot.componentOptions!;
const props = (opts.propsData || {}) as Record<string, unknown>;
return (
<tab-pane
class={this.ns.b('tab-item')}
tab={this.tabName}
label={props.caption}
name={slot.key}
>
{slot}
</tab-pane>
);
})}
</tabs>
);
},
});
import FormButton from './form-button/form-button';
import FormDRUIPart from './form-druipart/form-druipart';
import FormGroupPanel from './form-group-panel/form-group-panel';
import FormItemContainer from './form-item-container/form-item-container';
import FormItem from './form-item/form-item';
import FormPage from './form-page/form-page';
import FormPageItem from './form-page/form-page-item';
import FormRawItem from './form-raw-item/form-raw-item';
import FormTabPage from './form-tab-page/form-tab-page';
import FormTabPanel from './form-tab-panel/form-tab-panel';
import { FormControl } from './form-control';
export {
FormButton,
FormDRUIPart,
FormGroupPanel,
FormItemContainer,
FormItem,
FormPage,
FormPageItem,
FormRawItem,
FormTabPage,
FormTabPanel,
FormControl,
};
import {
GridEditItemController,
GridFieldColumnController,
GridRowController,
} from '@ibiz-template/controller';
import { computed, defineComponent } from 'vue';
export const GridColumn = defineComponent({
name: 'GridColumn',
props: {
fieldColumn: {
type: GridFieldColumnController,
required: true,
},
editItem: {
type: GridEditItemController,
},
row: {
type: GridRowController,
required: true,
},
},
setup(props) {
/** 先看列是否启用行编辑,然后是否有配置编辑项,最后才看rowController控的状态 */
const showRowEdit = computed(() => {
return (
props.fieldColumn.enableRowEdit &&
props.editItem &&
props.row.showRowEdit
);
});
return { showRowEdit };
},
render() {
if (this.showRowEdit && this.editItem) {
return (
<grid-edit-item
controller={this.editItem}
row={this.row}
></grid-edit-item>
);
}
return (
<grid-field-column
controller={this.fieldColumn}
row={this.row}
></grid-field-column>
);
},
});
export default GridColumn;
import { IBizContext } from '@ibiz-template/core';
import { GridModel } from '@ibiz-template/model';
import { useGridController, useNamespace } from '@ibiz-template/vue-util';
import { Table } from 'view-design';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
import { AppGridPagination } from '@/components/common';
import '@/styles/components/widgets/grid/grid.scss';
import {
useAppGridPagination,
useITableColumns,
useITableEvent,
} from './grid-control.util';
export const GridControl = defineComponent({
props: {
modelData: GridModel,
context: IBizContext,
params: { type: Object as PropType<IParams>, default: () => ({}) },
/**
* 表格行数据默认激活模式
* - 0 不激活
* - 1 单击激活
* - 2 双击激活(默认值)
*
* @type {(number | 0 | 1 | 2)}
*/
gridRowActiveMode: { type: Number, default: 2 },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const ns = useNamespace('grid');
const c = useGridController(
proxy,
props.modelData!,
props.context!,
props.params,
);
const [columns] = useITableColumns(c);
const { onRowClick, onDbRowClick, onSelectionChange } = useITableEvent(c);
const { onPageChange, onPageReset, onPageSizeChange } =
useAppGridPagination(c);
// iView表格单击行选中没有,必须手动调用选中方法
const onUIRowClick = (data: IData, index: number) => {
const gridInstance: IData | undefined = proxy.$refs.grid;
if (gridInstance) {
if (gridInstance.toggleSelect) {
gridInstance.toggleSelect(index);
}
if (gridInstance.highlightCurrentRow) {
gridInstance.highlightCurrentRow(index);
}
}
onRowClick(data);
};
return {
c,
ns,
columns,
onDbRowClick,
onUIRowClick,
onSelectionChange,
onPageChange,
onPageSizeChange,
onPageReset,
};
},
render() {
if (!this.c.complete) {
return;
}
// 绘制作用域插槽,绘制行编辑开启开关
const columnSlots: IData = {
rowEdit: ({ index }: IData) => {
return (
<i-button
icon='md-settings'
on-click={async (event: MouseEvent) => {
event.stopPropagation();
const rowController = this.c.rows[index];
await this.c.toggleRowEdit(rowController);
}}
nativeOn-dblclick={(e: MouseEvent) => {
e.stopPropagation();
}}
></i-button>
);
},
};
if (this.c.rows.length > 0) {
// 属性列自定义
this.c.model.fieldColumns.forEach(item => {
columnSlots[item.codeName] = ({ row, index }: IData) => {
return (
<grid-column
key={row.srfkey + item.codeName}
field-column={this.c.fieldColumns[item.codeName]}
edit-item={this.c.editItems[item.codeName]}
row={this.c.rows[index]}
></grid-column>
);
};
});
// 操作列自定义
this.c.model.uaColumns.forEach(item => {
columnSlots[item.codeName] = ({ row, index }: IData) => {
return (
<grid-ua-column
key={row.srfkey + item.codeName}
ua-column={this.c.uaColumns[item.codeName]}
row={this.c.rows[index]}
></grid-ua-column>
);
};
});
}
return (
<control-layout modelData={this.c.model}>
<div
class={[
this.ns.b(),
this.ns.is('show-header', !this.c.model.source.hideHeader),
this.ns.is('enable-page', this.c.model.source.enablePagingBar),
]}
>
<Table
ref={'grid'}
class={this.ns.b('content')}
show-header={!this.c.model.source.hideHeader}
highlight-row
data={this.c.items}
columns={this.columns}
on-on-row-click={this.onUIRowClick}
on-on-row-dblclick={this.onDbRowClick}
on-on-selection-change={this.onSelectionChange}
scopedSlots={columnSlots}
></Table>
{this.c.model.source.enablePagingBar && (
<AppGridPagination
total={this.c.total}
curPage={this.c.curPage}
size={this.c.size}
on-change={this.onPageChange}
on-page-size-change={this.onPageSizeChange}
on-page-reset={this.onPageReset}
></AppGridPagination>
)}
</div>
</control-layout>
);
},
});
/* eslint-disable no-param-reassign */
import { GridController } from '@ibiz-template/controller';
import { GridModel, GridUAColumnModel } from '@ibiz-template/model';
import { ControlVO } from '@ibiz-template/service';
import { computed, ref } from 'vue';
/**
* 生成iViewTable要用的columns
*
* @author lxm
* @date 2022-08-31 19:08:34
* @export
* @param {GridController} c
* @returns {*} {IData[]}
*/
export function generateIViewColumns(c: GridController): IData[] {
const gridModel: GridModel = c.model;
const columns: IData[] = gridModel.columns.map(column => {
const width = column.source.widthUnit === 'STAR' ? undefined : column.width;
return {
title: column.title,
width,
minWidth: column.width,
align: column.source.align?.toLowerCase() || 'center',
slot: column.codeName,
key: column.codeName,
ellipsis: true,
tooltip: false, // todo 表格提示用title
resizable: true,
};
});
// 多选的时候给第一列添加选择列
if (!c.singleSelect) {
columns.splice(0, 0, { type: 'selection', width: 60 });
}
// 配的操作列行为里是否有 表格界面_行编辑开关操作
let hasEditAction = false;
c.model.uaColumns.forEach((uaColumn: GridUAColumnModel) => {
const groupDetails = uaColumn.actionGroup?.getPSUIActionGroupDetails();
groupDetails?.forEach(detail => {
const action = detail.getPSUIAction();
if (action?.codeName === 'ToggleRowEdit') {
hasEditAction = true;
}
});
});
// 如果表格启用了行编辑 并且 行为里没有行编辑开关,出一列切换的
if (c.model.source.enableRowEdit && !hasEditAction) {
columns.push({
title: '行编辑',
key: 'rowEdit',
slot: 'rowEdit',
width: 80,
align: 'center',
});
}
return columns;
}
/**
* 获取iview的Table使用的columns响应式变量
*
* @author lxm
* @date 2022-08-18 19:08:22
* @export
* @param {GridController} c
* @returns [columns, originColumns 原始]
*/
export function useITableColumns(c: GridController) {
// 原始iview用列数据
const originColumns = ref<IData[]>([]);
c.hooks.complete.tap(() => {
originColumns.value = generateIViewColumns(c);
});
// 实际iview使用的columns
const columns = computed(() => {
// 先浅拷贝
const copy = [...originColumns.value];
// 如果没有自适应列,深拷贝最后一个对象,改成自适应
const flexColumn = copy.find(column => !!column.width);
if (flexColumn) {
// 修改自适应列的索引,默认最后一个
let index = copy.length - 1;
if (copy[index].key === 'rowEdit') {
index -= 1;
}
copy[index] = {
...copy[index],
width: undefined,
};
}
return copy;
});
return [columns, originColumns];
}
/**
* 适配iview的table的事件
*
* @author lxm
* @date 2022-09-05 21:09:42
* @export
* @param {GridController} c
* @returns {*}
*/
export function useITableEvent(c: GridController) {
const getOriginData = (data: IData): ControlVO => {
return c.items.find(item => item.srfkey === data.srfkey)!;
};
function onRowClick(data: IData) {
const origin = getOriginData(data);
// 单选的时候选中效果靠这个回调
if (c.singleSelect) {
c.onSelectionChange([origin]);
}
c.onRowClick(origin);
}
function onDbRowClick(data: IData) {
c.onDbRowClick(getOriginData(data));
}
function onSelectionChange(selection: IData[]) {
// 单选的时候选中效果靠点击事件
if (!c.singleSelect) {
const origins = selection.map(item => getOriginData(item));
c.onSelectionChange(origins);
}
}
return { onRowClick, onDbRowClick, onSelectionChange };
}
/**
* 使用表格分页组件
*
* @author lxm
* @date 2022-09-06 17:09:09
* @export
* @param {GridController} c
* @returns {*}
*/
export function useAppGridPagination(c: GridController) {
function onPageChange(page: number) {
if (!page || page === c.curPage) {
return;
}
c.curPage = page;
c.load();
}
function onPageSizeChange(size: number) {
if (!size || size === c.size) {
return;
}
c.size = size;
// 当page为第一页的时候切换size不会触发pageChange,需要自己触发加载
if (c.curPage === 1) {
c.load();
}
}
function onPageReset() {
c.curPage = 1;
c.load();
}
return { onPageChange, onPageSizeChange, onPageReset };
}
import {
GridEditItemController,
GridRowController,
} from '@ibiz-template/controller';
import { defineComponent } from 'vue';
export const GridEditItem = defineComponent({
name: 'GridEditItem',
props: {
controller: {
type: GridEditItemController,
required: true,
},
row: {
type: GridRowController,
required: true,
},
},
setup(props) {
// 编辑器值变更事件
const onValueChange = (val: unknown, name?: string) => {
props.controller.setRowValue(props.row, val, name);
};
const onStopPropagation = (e: MouseEvent) => {
e.stopPropagation();
};
return { onValueChange, onStopPropagation };
},
render(h) {
const codeName = this.controller.model.codeName;
return (
<appGridEditItem
required={this.controller.required}
error={this.row.errors[codeName]}
nativeondblclick={this.onStopPropagation}
nativeonClick={this.onStopPropagation}
>
{this.controller.editorProvider &&
h(this.controller.editorProvider.rowEditor, {
props: {
value: this.row.errors[codeName],
data: this.row.data,
controller: this.controller.editor,
disable: this.row.editItemState[codeName]!.disabled,
},
on: {
change: (val: unknown, name?: string) => {
this.onValueChange(val, name);
},
},
})}
</appGridEditItem>
);
},
});
export default GridEditItem;
import {
GridFieldColumnController,
GridRowController,
} from '@ibiz-template/controller';
import { defineComponent } from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
export const GridFieldColumn = defineComponent({
name: 'GridFieldColumn',
props: {
controller: {
type: GridFieldColumnController,
required: true,
},
row: {
type: GridRowController,
required: true,
},
},
setup() {
const ns = useNamespace('grid-column');
return { ns };
},
render() {
return (
<span class={this.ns.b()}>
{this.row.data[this.controller.model.codeName]}
</span>
);
},
});
export default GridFieldColumn;
import {
GridUAColumnController,
GridRowController,
} from '@ibiz-template/controller';
import { IPSUIActionGroupDetail } from '@ibiz-template/model';
import { useNamespace } from '@ibiz-template/vue-util';
import { defineComponent } from 'vue';
import '@/styles/components/widgets/grid/grid-ua-column.scss';
export const GridUAColumn = defineComponent({
name: 'GridUAColumn',
props: {
uaColumn: {
type: GridUAColumnController,
required: true,
},
row: {
type: GridRowController,
required: true,
},
},
setup(props) {
const ns = useNamespace('grid-ua-column');
const onStopPropagation = (e: MouseEvent) => {
e.stopPropagation();
};
const onActionClick = async (
detail: IPSUIActionGroupDetail,
event: MouseEvent,
) => {
await props.uaColumn.onActionClick(detail, props.row, event);
};
return { ns, onStopPropagation, onActionClick };
},
render() {
return (
<div
class={this.ns.b()}
on-dblclick={this.onStopPropagation}
onClick={this.onStopPropagation}
>
{!this.uaColumn.model.actionGroup ? null : (
<actionToolbar
action-group={this.uaColumn.model.actionGroup}
action-states={
this.row.uaColumnStates[this.uaColumn.model.codeName]
}
on-action-click={this.onActionClick}
></actionToolbar>
)}
</div>
);
},
});
export default GridUAColumn;
import { GridControl } from './grid-control';
import GridColumn from './grid-column/grid-column';
import GridUAColumn from './grid-ua-column/grid-ua-column';
import GridFieldColumn from './grid-field-column/grid-field-column';
import GridEditItem from './grid-edit-item/grid-edit-item';
export { GridControl, GridColumn, GridUAColumn, GridFieldColumn, GridEditItem };
import EditFormControl from './edit-form-control/edit-form-control';
import SearchFormControl from './search-form-control/search-form-control.vue';
export * from './form-control';
export * from './grid-control';
export * from './pickup-view-panel/pickup-view-panel';
export * from './view-panel/view-panel';
export { AppMenu } from './app-menu/app-menu';
export { EditFormControl, SearchFormControl };
import { IBizContext } from '@ibiz-template/core';
import { ViewPanelModel } from '@ibiz-template/model';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
import { usePickupViewPanelController } from '@ibiz-template/vue-util';
import { IModal, ViewMode } from '@ibiz-template/runtime';
import { getViewComponentName } from '@/util';
export const PickupViewPanel = defineComponent({
props: {
modelData: {
type: ViewPanelModel,
required: true,
},
context: {
type: IBizContext,
required: true,
},
params: { type: Object as PropType<IParams>, default: () => ({}) },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = usePickupViewPanelController(
proxy,
props.modelData,
props.context,
props.params,
);
// 模态对象
const modal: IModal = { mode: ViewMode.EMBED };
// 视图组件名称和视图模型路径
const viewComponentName = getViewComponentName(
c.model.embedView.source.viewType,
);
const viewPath = c.model.embedView.source.modelPath;
return { c, modal, viewComponentName, viewPath };
},
render(h) {
if (!this.c.complete) {
return;
}
return h(this.viewComponentName, {
props: {
context: this.c.context,
params: this.c.params,
modal: this.modal,
modelPath: this.viewPath,
},
on: {
neuronInit: this.c.nerve.onNeuronInit('embedView'),
},
});
},
});
<script setup lang="ts">
import { IBizContext } from '@ibiz-template/core';
import { SearchFormModel } from '@ibiz-template/model';
import { useSearchFormController } from '@ibiz-template/vue-util';
import { getCurrentInstance } from 'vue';
interface SearchFormControlProps {
modelData: SearchFormModel;
context: IBizContext;
params: IParams;
}
const props = withDefaults(defineProps<SearchFormControlProps>(), {});
const { proxy } = getCurrentInstance()!;
useSearchFormController(proxy, props.modelData, props.context, props.params);
</script>
<template>
<div>搜索表单</div>
</template>
import { IBizContext } from '@ibiz-template/core';
import { ViewPanelModel } from '@ibiz-template/model';
import { useViewPanelController } from '@ibiz-template/vue-util';
import { defineComponent, getCurrentInstance, PropType } from 'vue';
import { IModal, ViewMode } from '@ibiz-template/runtime';
import { getViewComponentName } from '@/util';
export const ViewPanel = defineComponent({
props: {
modelData: {
type: ViewPanelModel,
required: true,
},
context: {
type: IBizContext,
required: true,
},
params: { type: Object as PropType<IParams>, default: () => ({}) },
},
setup(props) {
const { proxy } = getCurrentInstance()!;
const c = useViewPanelController(
proxy,
props.modelData,
props.context,
props.params,
);
// 模态对象
const modal: IModal = { mode: ViewMode.EMBED };
// 视图组件名称和视图模型路径
const viewComponentName = getViewComponentName(
c.model.embedView.source.viewType,
);
const viewPath = c.model.embedView.source.modelPath;
return { c, modal, viewComponentName, viewPath };
},
render(h) {
if (!this.c.complete) {
return;
}
return h(this.viewComponentName, {
props: {
context: this.c.context,
params: this.c.params,
modal: this.modal,
modelPath: this.viewPath,
},
on: {
neuronInit: this.c.nerve.onNeuronInit('embedView'),
},
});
},
});
import { OrgData } from '@ibiz-template/core';
import { ModelUtil } from '@ibiz-template/model';
import router from '@/router';
/**
* 初始化模型
*
* @author chitanda
* @date 2022-07-20 20:07:58
* @return {*} {Promise<void>}
*/
async function loadModel(): Promise<void> {
await ModelUtil.create(async modelPath => {
const url = `${ibiz.env.remoteModelUrl}${modelPath}`;
try {
const res = await ibiz.net.get(url);
if (res.ok) {
return res.data as IModel;
}
} catch (error) {
console.log(error);
}
return {};
});
}
/**
* 加载应用数据
*
* @author chitanda
* @date 2022-07-20 20:07:50
* @return {*} {Promise<void>}
*/
async function loadAppData(): Promise<void> {
const res = await ibiz.net.get('/appdata');
if (res.ok) {
ibiz.appData = res.data;
}
}
/**
* 加载组织数据
*
* @author chitanda
* @date 2022-07-20 20:07:44
* @return {*} {Promise<void>}
*/
async function loadOrgData(): Promise<void> {
const res = await ibiz.net.get(`/uaa/getbydcsystem/${ibiz.env.dcSystem}`);
if (res.ok) {
const orgDataItems = res.data as OrgData[];
if (orgDataItems) {
const [data] = orgDataItems;
ibiz.orgData = data;
}
}
}
/**
* 应用参数初始化
*
* @author chitanda
* @date 2022-07-20 19:07:54
* @return {*} {Promise<void>}
*/
async function appInit(): Promise<void> {
try {
await loadOrgData();
await loadAppData();
await loadModel();
} catch (error) {
const { response, message } = error as IData;
if (response.status === 401) {
router.push('/login');
}
console.log(`应用参数初始化失败:${message}`);
}
}
export async function AuthGuard(): Promise<boolean> {
await appInit();
return true;
}
export { AuthGuard } from './auth-guard/auth-guard';
export interface AppDrawerOptions {
/**
* 抽屉宽度
* 左、右方向时可用。当其值不大于 100 时以百分比显示,大于 100 时为像素
*
* @author lxm
* @date 2022-09-12 20:09:20
* @type {string | number}
*/
width?: string | number;
/**
* 抽屉高度
* 上、下方向时可用。当其值不大于 100 时以百分比显示,大于 100 时为像素
*
* @author lxm
* @date 2022-09-12 20:09:22
* @type {string | number}
*/
height?: string | number;
/**
* 抽屉的方向
*
* @author lxm
* @date 2022-09-15 14:09:13
* @type {('left' | 'right' | 'top' | 'bottom')}
*/
placement?: 'left' | 'right' | 'top' | 'bottom';
}
export interface AppModalOptions {
/**
* 宽度
*
* @author lxm
* @date 2022-09-12 20:09:20
* @type {string | number}
*/
width?: string | number;
/**
* 高度
*
* @author lxm
* @date 2022-09-12 20:09:22
* @type {string | number}
*/
height?: string | number;
/**
* 是否隐藏底部按钮(默认false)
*
* @author lxm
* @date 2022-09-12 20:09:23
* @type {boolean}
*/
footerHide?: boolean;
}
export * from './app-modal/app-modal-opts';
export * from './app-drawer/app-drawer-opts';
import '@/styles/index.scss';
import 'systemjs';
import Vue from 'vue';
import Router from 'vue-router';
import { PiniaVuePlugin } from 'pinia';
import router from './router';
import App from './App';
import { AppRegister } from './app-register';
import {
MessageUtil,
OpenViewUtil,
ModalUtil,
NotificationUtil,
LoadingUtil,
ErrorHandler,
} from './util';
import { piniaInstance } from './store';
import { attachEnvironmentConfig } from './attach-environment-config';
Vue.use(Router);
Vue.use(PiniaVuePlugin);
Vue.use(AppRegister);
/** 异常处理:start */
Vue.config.errorHandler = function (err: Error, _vm) {
ErrorHandler.handlerError(err);
};
window.onerror = function (
event: Event | string,
source?: string,
lineno?: number,
colno?: number,
error?: Error,
) {
if (error) {
ErrorHandler.handlerError(error);
}
};
window.addEventListener('unhandledrejection', function (event) {
// 阻止继续冒泡
event.preventDefault();
// 获取到未处理的promise对象
event.promise.catch(err => {
ErrorHandler.handlerError(err);
});
});
/** 异常处理:end */
async function createApp(): Promise<void> {
await attachEnvironmentConfig();
const app = new Vue({
created() {
ibiz.openView = new OpenViewUtil();
ibiz.message = new MessageUtil();
ibiz.modal = new ModalUtil();
ibiz.notification = new NotificationUtil();
ibiz.loading = new LoadingUtil();
},
router,
pinia: piniaInstance,
render: h => h(App),
});
app.$mount('#app');
}
createApp();
import { CheckBoxListModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { CheckBoxListEditorController } from '@ibiz-template/controller/src/editor';
/**
* 多选框列表编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class CheckBoxListEditorProvider
* @implements {EditorProvider}
*/
export class CheckBoxListEditorProvider
implements IEditorProvider<CheckBoxListEditorController>
{
formEditor: string = 'IBizCheckBoxList';
rowEditor: string = 'IBizCheckBoxList';
async createController(
editorModel: CheckBoxListModel,
parentController: FormItemController | GridEditItemController,
): Promise<CheckBoxListEditorController> {
const c = new CheckBoxListEditorController(editorModel, parentController);
await c.init();
return c;
}
}
import { PickerEditorModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { PickerEditorController } from '@ibiz-template/controller/src/editor';
/**
* 数据选择器编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class DataPickerEditorProvider
* @implements {EditorProvider}
*/
export class DataPickerEditorProvider
implements IEditorProvider<PickerEditorController>
{
formEditor: string;
rowEditor: string;
constructor(editorType: string) {
let componentName = 'IBizPicker';
switch (editorType) {
case 'PICKEREX_TRIGGER':
componentName = 'IBizPickerDropDown';
break;
case 'PICKEREX_LINK':
componentName = 'AppPickerLinkOnly';
break;
case 'ADDRESSPICKUP':
componentName = 'IBizMPicker';
break;
default:
}
this.formEditor = componentName;
this.rowEditor = componentName;
}
async createController(
editorModel: PickerEditorModel,
parentController: FormItemController | GridEditItemController,
): Promise<PickerEditorController> {
const c = new PickerEditorController(editorModel, parentController);
await c.init();
return c;
}
}
import { DatePickerEditorModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { DatePickerEditorController } from '@ibiz-template/controller/src/editor';
/**
* 日期时间选择器编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class DatePickerEditorProvider
* @implements {EditorProvider}
*/
export class DatePickerEditorProvider
implements IEditorProvider<DatePickerEditorController>
{
formEditor: string = 'IBizDatePicker';
rowEditor: string = 'IBizDatePicker';
async createController(
editorModel: DatePickerEditorModel,
parentController: FormItemController | GridEditItemController,
): Promise<DatePickerEditorController> {
const c = new DatePickerEditorController(editorModel, parentController);
await c.init();
return c;
}
}
import { DropDownListModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { DropDownListEditorController } from '@ibiz-template/controller/src/editor';
/**
* 多选框列表编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class DropDownListEditorProvider
* @implements {EditorProvider}
*/
export class DropDownListEditorProvider
implements IEditorProvider<DropDownListEditorController>
{
formEditor: string = 'IBizDropDownList';
rowEditor: string = 'IBizDropDownList';
async createController(
editorModel: DropDownListModel,
parentController: FormItemController | GridEditItemController,
): Promise<DropDownListEditorController> {
const c = new DropDownListEditorController(editorModel, parentController);
await c.init();
return c;
}
}
import { FileUploaderEditorModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { UploadEditorController } from '@ibiz-template/controller/src/editor';
/**
* 文件上传编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class FileUploaderEditorProvider
* @implements {EditorProvider}
*/
export class FileUploaderEditorProvider
implements IEditorProvider<UploadEditorController>
{
formEditor: string = 'IBizFileUpload';
rowEditor: string = 'IBizFileUpload';
constructor(editorType: string) {
if (editorType === 'PICTURE') {
this.formEditor = 'AppImageUpload';
this.rowEditor = 'AppImageUpload';
} else if (editorType === 'FILEUPLOADER') {
this.formEditor = 'IBizFileUpload';
this.rowEditor = 'AppFileUploadRowPreview';
}
}
async createController(
editorModel: FileUploaderEditorModel,
parentController: FormItemController | GridEditItemController,
): Promise<UploadEditorController> {
const c = new UploadEditorController(editorModel, parentController);
await c.init();
return c;
}
}
import { SpanEditorProvider } from './span-provider';
import { TextBoxEditorProvider } from './text-box-provider';
import { CheckBoxListEditorProvider } from './check-box-list-provider';
import { RadioButtonListEditorProvider } from './radio-button-list-provider';
import { DropDownListEditorProvider } from './drop-down-list-provider';
import { DatePickerEditorProvider } from './date-picker-provider';
import { FileUploaderEditorProvider } from './file-uploader-provider';
import { DataPickerEditorProvider } from './data-picker-provider';
/**
* 预置默认的编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:50
* @export
*/
export function presetEditorProvider(): void {
const { editorRegister } = ibiz.register;
if (!editorRegister) {
return;
}
// 标签
editorRegister.register('SPAN', new SpanEditorProvider());
// 输入框
const textBoxEditorProvider = new TextBoxEditorProvider();
editorRegister.register('TEXTBOX', textBoxEditorProvider);
editorRegister.register('TEXTAREA', textBoxEditorProvider);
editorRegister.register('TEXTAREA_10', textBoxEditorProvider);
editorRegister.register('PASSWORD', textBoxEditorProvider);
editorRegister.register('NUMBER', new TextBoxEditorProvider('NUMBER'));
// 复选框列表
editorRegister.register('CHECKBOXLIST', new CheckBoxListEditorProvider());
// 单选框列表
editorRegister.register(
'RADIOBUTTONLIST',
new RadioButtonListEditorProvider(),
);
// 下拉列表
const dropDownListProvider = new DropDownListEditorProvider();
editorRegister.register('DROPDOWNLIST', dropDownListProvider);
editorRegister.register('MDROPDOWNLIST', dropDownListProvider);
// 日期选择器
const datePickerProvider = new DatePickerEditorProvider();
editorRegister.register('DATEPICKER', datePickerProvider);
editorRegister.register('DATEPICKEREX', datePickerProvider);
editorRegister.register('DATEPICKEREX_NOTIME', datePickerProvider);
editorRegister.register('DATEPICKEREX_HOUR', datePickerProvider);
editorRegister.register('DATEPICKEREX_MINUTE', datePickerProvider);
editorRegister.register('DATEPICKEREX_SECOND', datePickerProvider);
editorRegister.register('DATEPICKEREX_NODAY', datePickerProvider);
editorRegister.register('DATEPICKEREX_NODAY_NOSECOND', datePickerProvider);
// 文件上传
editorRegister.register(
'FILEUPLOADER',
new FileUploaderEditorProvider('FILEUPLOADER'),
);
// 图片上传
editorRegister.register('PICTURE', new FileUploaderEditorProvider('PICTURE'));
// 数据选择类
editorRegister.register('PICKER', new DataPickerEditorProvider('PICKER'));
editorRegister.register(
'PICKEREX_NOAC',
new DataPickerEditorProvider('PICKEREX_NOAC'),
);
editorRegister.register(
'PICKEREX_TRIGGER',
new DataPickerEditorProvider('PICKEREX_TRIGGER'),
);
editorRegister.register(
'PICKEREX_LINK',
new DataPickerEditorProvider('PICKEREX_LINK'),
);
editorRegister.register(
'ADDRESSPICKUP',
new DataPickerEditorProvider('ADDRESSPICKUP'),
);
}
export {
SpanEditorProvider,
TextBoxEditorProvider,
CheckBoxListEditorProvider,
RadioButtonListEditorProvider,
DatePickerEditorProvider,
FileUploaderEditorProvider,
DataPickerEditorProvider,
};
import { RadioButtonListModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { RadioButtonListEditorController } from '@ibiz-template/controller/src/editor';
/**
* 单选框列表编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class RadioButtonListEditorProvider
* @implements {EditorProvider}
*/
export class RadioButtonListEditorProvider
implements IEditorProvider<RadioButtonListEditorController>
{
formEditor: string = 'IBizRadioButtonList';
rowEditor: string = 'IBizRadioButtonList';
async createController(
editorModel: RadioButtonListModel,
parentController: FormItemController | GridEditItemController,
): Promise<RadioButtonListEditorController> {
const c = new RadioButtonListEditorController(
editorModel,
parentController,
);
await c.init();
return c;
}
}
import { SpanEditorModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import { FormItemController } from '@ibiz-template/controller/src/control/form';
import { GridEditItemController } from '@ibiz-template/controller/src/control/grid';
import { SpanEditorController } from '@ibiz-template/controller/src/editor';
/**
* 标签编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class SpanEditorProvider
* @implements {EditorProvider}
*/
export class SpanEditorProvider
implements IEditorProvider<SpanEditorController>
{
formEditor: string = 'IBizSpan';
rowEditor: string = 'IBizSpan';
async createController(
editorModel: SpanEditorModel,
parentController: FormItemController | GridEditItemController,
): Promise<SpanEditorController> {
const c = new SpanEditorController(editorModel, parentController);
await c.init();
return c;
}
}
import { TextBoxEditorModel } from '@ibiz-template/model';
import { IEditorProvider } from '@ibiz-template/runtime';
import {
FormItemController,
GridEditItemController,
TextBoxEditorController,
} from '@ibiz-template/controller';
/**
* 输入框编辑器适配器
*
* @author lxm
* @date 2022-09-19 22:09:03
* @export
* @class TextBoxEditorProvider
* @implements {EditorProvider}
*/
export class TextBoxEditorProvider
implements IEditorProvider<TextBoxEditorController>
{
formEditor: string = 'IBizInputBox';
rowEditor: string = 'IBizInputBox';
constructor(editorType?: string) {
if (editorType === 'NUMBER') {
this.formEditor = 'AppInputNumber';
this.rowEditor = 'AppInputNumber';
}
}
async createController(
editorModel: TextBoxEditorModel,
parentController: FormItemController | GridEditItemController,
): Promise<TextBoxEditorController> {
const c = new TextBoxEditorController(editorModel, parentController);
await c.init();
return c;
}
}
export * from './editor/index';
import Router from 'vue-router';
import { AuthGuard } from '../guard';
import RouterShell from '@/components/router-shell/router-shell';
const router = new Router({
routes: [
{
path: '/',
redirect: '/index',
},
{
path: '/login',
name: 'loginView',
component: () => import('../views/login-view/login-view'),
},
{
path: '/404',
name: '404View',
component: () => import('../views/404-view/404-view'),
},
{
path: '/:view1([^=/]+)/:params1([^/]+=[^/]+)?',
props: {
level: 1,
},
beforeEnter: async (_to, _from, next) => {
const authority = await AuthGuard();
if (authority) {
next();
} else {
next(false);
}
},
component: RouterShell,
children: [
{
path: ':view2([^=/]+)/:params2([^/]+=[^/]+)?',
props: {
level: 2,
},
component: RouterShell,
children: [
{
path: ':view3([^=/]+)/:params3([^/]+=[^/]+)?',
props: {
level: 3,
},
component: RouterShell,
},
],
},
],
},
{
path: '*',
redirect: '/404',
},
],
});
export default router;
import { defineStore } from 'pinia';
import { reactive } from 'vue';
export interface IAppStore {}
export const useAppStore = defineStore('appStore', () => {
const appStore = reactive<IAppStore>({});
return { appStore };
});
import { createPinia } from 'pinia';
export * from './app-store/app-store';
export * from './ui-store/ui-store';
export const piniaInstance = createPinia();
import { defineStore } from 'pinia';
import { reactive, Ref } from 'vue';
import { useZIndexStore } from './z-index';
export interface IUIState {
/**
* ui层级
*
* @author lxm
* @date 2022-08-18 21:08:48
* @type {number}
*/
zIndex: Ref<number>;
}
export const useUIStore = defineStore('uiStore', () => {
const zIndex = useZIndexStore();
const UIStore = reactive<IUIState>({
zIndex: zIndex.zIndex,
});
return { UIStore, zIndex };
});
import { Ref, ref } from 'vue';
export interface IzIndexStore {
/**
* 当前最高的ui层级
*
* @author lxm
* @date 2022-08-18 21:08:48
* @type {number}
*/
zIndex: Ref<number>;
/**
* 增加Ui层级,返回增加后的层级
*
* @author lxm
* @date 2022-08-18 21:08:22
*/
increment(): number;
/**
* 减少Ui层级
*
* @author lxm
* @date 2022-08-18 21:08:22
*/
decrement(): void;
}
/**
* 定义zIndex的全局状态变量
*
* @author lxm
* @date 2022-08-18 21:08:26
* @export
* @returns {*}
*/
export function useZIndexStore(): IzIndexStore {
const DEFAULT_INDEX = 500;
const INCREMENT_VALUE = 1;
const zIndex = ref(DEFAULT_INDEX);
function increment(): number {
zIndex.value += INCREMENT_VALUE;
return zIndex.value;
}
function decrement() {
zIndex.value -= INCREMENT_VALUE;
}
return { zIndex, increment, decrement };
}
@include b(grid-page) {
display: flex;
@include b(grid-page-btn) {
margin-right: 10px;
}
}
/* 页面切换动画-s */
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
will-change: transform;
transition: all 0.3s ease;
}
// slide
.slide-right-enter-from,
.slide-left-leave-to {
opacity: 0;
transform: translateX(-20px);
}
.slide-right-leave-to,
.slide-left-enter-from {
opacity: 0;
transform: translateX(20px);
}
/* 页面切换动画-e */
@include b(app-user) {
margin-right: 25px;
}
@include b(app-user-avatar) {
cursor: pointer;
@include e(name) {
margin-left: 12px;
}
i{
margin-right: 5px;
}
}
@include b(app-user-avatar-wrapper) {
display: block;
padding: 0 12px;
&:hover {
background: hsl(0deg 0% 100% / 5%);
}
}
@include b(quick-search) {
@include set-component-css-var('quick-search-embed', $quick-search-embed);
@include m(embed) {
.ivu-icon {
height: getCssVar('quick-search-embed', 'height');
line-height: getCssVar('quick-search-embed', 'line-height');
}
.ivu-input {
height: getCssVar('quick-search-embed', 'height');
line-height: getCssVar('quick-search-embed', 'line-height');
}
}
}
@include b(skeleton-card) {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
@include b(skeleton-card-header) {
width: 100%;
@include e(title) {
width: 50%;
height: 16px;
line-height: 16px;
margin-bottom: 24px;
border-radius: 2px;
background: $skeleton-color;
}
}
@include b(skeleton-card-content) {
flex: 1;
overflow: hidden;
@include e(item) {
width: 100%;
height: 16px;
line-height: 16px;
margin-bottom: 12px;
border-radius: 2px;
background: $skeleton-color;
&:last-child {
margin-bottom: 0;
}
}
}
@include m(active) {
@include b(skeleton-card-header) {
@include e(title) {
position: relative;
z-index: 0;
overflow: hidden;
background: transparent;
&::after {
content: '';
position: absolute;
top: 0;
right: -150%;
bottom: 0;
left: -150%;
background: linear-gradient(
90deg,
$skeleton-color 25%,
$skeleton-transition-color 37%,
$skeleton-color 63%
);
animation: skeleton-element--loading 1.4s ease infinite;
}
}
}
@include b(skeleton-card-content) {
@include e(item) {
position: relative;
z-index: 0;
overflow: hidden;
background: transparent;
&::after {
content: '';
position: absolute;
top: 0;
right: -150%;
bottom: 0;
left: -150%;
background: linear-gradient(
90deg,
$skeleton-color 25%,
$skeleton-transition-color 37%,
$skeleton-color 63%
);
animation: skeleton-element--loading 1.4s ease infinite;
}
}
}
}
@include m(round) {
@include b(skeleton-card-header) {
@include e(title) {
border-radius: 100px;
}
}
@include b(skeleton-card-content) {
@include e(item) {
border-radius: 100px;
}
}
}
@keyframes skeleton-element--loading {
0% {
transform: translateX(-37.5%);
}
100% {
transform: translateX(37.5%);
}
}
}
@include b(skeleton-element) {
display: block;
width: 100%;
height: 100%;
@include b(skeleton-input) {
display: block;
background: $skeleton-color;
width: 100%;
height: 100%;
line-height: 100%;
@include m(large) {
width: 200px;
min-width: 200px;
height: 40px;
line-height: 40px;
}
@include m(medium) {
width: 160px;
min-width: 160px;
height: 32px;
line-height: 32px;
}
@include m(small) {
width: 120px;
min-width: 120px;
height: 24px;
line-height: 24px;
}
}
@include m(active) {
@include b(skeleton-input) {
position: relative;
z-index: 0;
overflow: hidden;
background: transparent;
&::after {
content: '';
position: absolute;
top: 0;
right: -150%;
bottom: 0;
left: -150%;
background: linear-gradient(
90deg,
$skeleton-color 25%,
$skeleton-transition-color 37%,
$skeleton-color 63%
);
animation: skeleton-element--loading 1.4s ease infinite;
}
}
}
@keyframes skeleton-element--loading {
0% {
transform: translateX(-37.5%);
}
100% {
transform: translateX(37.5%);
}
}
}
.#{bem('view-toolbar')} {
@include set-component-css-var('view-toolbar', $view-toolbar);
}
@include b('view-toolbar') {
display: flex;
@include b('view-toolbar-item') {
display: flex;
align-items: center;
margin: getCssVar('view-toolbar', 'item-margin');
.ivu-btn {
@include flex(row, center, center);
> span {
@include flex(row, center, center);
}
}
img,
i {
display: inline-block;
max-width: getCssVar('view-toolbar', 'icon-max-width');
max-height: getCssVar('view-toolbar', 'icon-max-height');
margin: getCssVar('view-toolbar', 'icon-margin');
}
}
@include m(embed) {
@include set-component-css-var('view-toolbar-embed', $view-toolbar-embed);
@include b('view-toolbar-item') {
.ivu-btn {
height: getCssVar('view-toolbar-embed', 'item-height');
line-height: getCssVar('view-toolbar-embed', 'item-line-height');
border: none;
box-shadow: none;
}
img,
i {
// margin: 0;
}
}
}
}
// .#{bem('app-image-upload')} {
// @include set-component-css-var('app-image-upload', $app-image-upload);
// }
@include b('app-image-upload') {
display: flex;
align-items: center;
.ivu-upload {
display: inline-block;
height: getCssVar('app-image-upload', 'box-height');
}
@include e('btn') {
width: getCssVar('app-image-upload', 'box-width');
height: getCssVar('app-image-upload', 'box-height');
line-height: getCssVar('app-image-upload', 'box-height');
}
@include e('list-item') {
position: relative;
display: inline-block;
width: getCssVar('app-image-upload', 'box-width');
height: getCssVar('app-image-upload', 'box-height');
margin-right: getCssVar('app-image-upload', 'list-item-margin');
line-height: getCssVar('app-image-upload', 'box-height');
text-align: center;
background: getCssVar('app-image-upload', 'list-item-bg-color');
border: getCssVar('app-image-upload', 'list-item-border');
border-radius: getCssVar('app-image-upload', 'list-item-border-radius');
box-shadow: getCssVar('app-image-upload', 'list-item-box-shadow');
img {
width: 100%;
height: 100%;
vertical-align: top;
}
&:hover {
@include e('list-item-cover') {
display: block;
}
}
}
@include e('list-item-cover') {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
background: getCssVar('app-image-upload', 'list-item-cover-bg-color');
i {
margin: getCssVar('app-image-upload', 'list-item-cover-i-margin');
font-size: getCssVar('app-image-upload', 'list-item-cover-i-size');
color: getCssVar('app-image-upload', 'list-item-cover-i-color');
cursor: pointer;
}
img {
width: 100%;
height: 100%;
vertical-align: top;
}
}
}
@include b('app-image-upload-upload-model') {
@include e('model-img') {
width: 100%;
}
}
.#{bem('check-box-list','text')}{
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
}
\ No newline at end of file
@include b('date-picker') {
input {
height: getCssVar('form-item', 'line-height');
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
line-height: getCssVar('form-item', 'line-height');
color: getCssVar('form-item', 'color');
border-color: getCssVar('form-item', 'border-color');
}
input::placeholder {
color: getCssVar('form-item', 'placeholder-color');
}
input[disabled] {
color: getCssVar('form-item', 'disable-color');
background-color: getCssVar('form-item', 'disable-bg-color');
border-color: getCssVar('form-item', 'disable-border-color');
}
}
@mixin sameattribute {
height: getCssVar('form-item', 'line-height');
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
line-height: getCssVar('form-item', 'line-height');
border-color: getCssVar('form-item', 'border-color');
}
@include b('dropdown-list') {
.ivu-select-selection .ivu-select-selected-value {
color: getCssVar('form-item', 'color');
@include sameattribute;
}
.ivu-select-selection .ivu-select-placeholder {
color: getCssVar('form-item', 'placeholder-color');
@include sameattribute;
}
.ivu-select-selection {
.ivu-tag:hover {
opacity: 1;
}
.ivu-tag.ivu-tag-checked {
.ivu-tag-text {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
&:hover {
color: getCssVar('form-item', 'hover-color');
}
}
}
}
}
@include b('file-upload') {
@include b('file-upload-button') {
span {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
}
}
.ivu-btn:hover {
span {
color: getCssVar('form-item', 'hover-color');
}
}
.ivu-upload-list {
@include flex(row, flex-start, flex-start, wrap);
}
.ivu-upload-list-file {
@include flex(row, flex-start, center, nowrap);
flex: 0 0 auto;
&:hover {
background: getCssVar('form-item', 'disable-bg-color');
span {
color: getCssVar('form-item', 'hover-color');
}
}
}
}
@include b('input-box-input') {
input {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
line-height: getCssVar('form-item', 'line-height');
color: getCssVar('form-item', 'color');
border-color: getCssVar('form-item', 'border-color');
}
input::placeholder {
color: getCssVar('form-item', 'placeholder-color');
}
input[disabled] {
color: getCssVar('form-item', 'disable-color');
background-color: getCssVar('form-item', 'disable-bg-color');
border-color: getCssVar('form-item', 'disable-border-color');
}
textarea {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
border-color: getCssVar('form-item', 'border-color');
}
textarea[disabled] {
color: getCssVar('form-item', 'disable-color');
background-color: getCssVar('form-item', 'disable-bg-color');
border-color: getCssVar('form-item', 'disable-border-color');
}
textarea::placeholder {
color: getCssVar('form-item', 'placeholder-color');
}
}
@include b('mpicker') {
display: flex;
input::placeholder {
color: getCssVar('form-item', 'placeholder-color');
}
.ivu-select-selection {
.ivu-tag:hover {
opacity: 1;
}
.ivu-tag.ivu-tag-checked {
.ivu-tag-text {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
&:hover {
color: getCssVar('form-item', 'hover-color');
}
}
}
}
}
@include b('picker-dropdown') {
input {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
line-height: getCssVar('form-item', 'line-height');
color: getCssVar('form-item', 'color');
border-color: getCssVar('form-item', 'border-color');
}
input::placeholder {
color: getCssVar('form-item', 'placeholder-color');
}
}
// .#{bem('app-picker','transfer-item')} {
// @include set-component-css-var(
// 'app-picker-transfer-item',
// $app-picker-transfer-item
// );
// }
.#{bem('picker','autocomplete')} {
display: flex;
.ivu-select-dropdown-list {
max-height: 300px;
}
}
.#{bem('picker','btns')} {
display: flex;
}
.#{bem('picker','transfer-item')} {
padding: 5px;
cursor: pointer;
&:hover {
background-color: getCssVar('picker-transfer-item', 'hover-bg-color');
}
}
@include b('picker') {
input {
height: getCssVar('form-item', 'line-height');
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
line-height: getCssVar('form-item', 'line-height');
color: getCssVar('form-item', 'color');
border-color: getCssVar('form-item', 'border-color');
}
input::placeholder {
color: getCssVar('form-item', 'placeholder-color');
}
}
.#{bem('radio-button-list-radio-group','text')} {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
}
@include b('span') {
font-family: getCssVar('form-item', 'font-family');
font-size: getCssVar('form-item', 'font-size');
color: getCssVar('form-item', 'color');
}
@include b(not-supported-editor) {
color: getCssVar('color', 'danger');
}
\ No newline at end of file
@include b(layout) {
@include set-component-css-var('layout', $app-layout);
@include set-component-css-var('layout-nav', $app-layout-nav);
@include set-component-css-var('layout-header', $app-layout-header);
@include set-component-css-var('layout-content', $app-layout-content);
justify-content: flex-start;
width: 100vw;
height: 100vh;
overflow: hidden;
@include e(logo) {
@include flex-center;
width: getCssVar('layout-nav', 'width');
height: getCssVar('layout-header', 'height');
}
@include e(logo-caption) {
font-size: getCssVar('layout', 'caption-font-size');
}
@include when(collapse) {
@include b(layout-content) {
padding-left: getCssVar('layout-nav', 'width-collapse');
}
}
}
@include b(layout-nav) {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: getCssVar('layout-nav', 'bg-color');
box-shadow: getCssVar('layout-nav', 'box-shadow');
}
@include b(layout-header) {
position: absolute;
top: 0;
right: 0;
z-index: 14;
display: flex;
justify-content: space-between;
width: 100%;
height: getCssVar('layout-header', 'height');
padding: 0;
line-height: getCssVar('layout-header', 'height');
color: getCssVar('layout-header', 'text-color');
background-color: getCssVar('layout-header', 'bg-color');
box-shadow: getCssVar('layout-header', 'box-shadow');
@include e(left) {
display: flex;
}
@include e(icon) {
font-size: getCssVar('layout-header', 'icon-font-size');
cursor: pointer;
}
@include e(back-icon) {
height: 100%;
margin: getCssVar('layout-header', 'back-icon-margin');
font-size: getCssVar('layout-header', 'back-icon-font-size');
cursor: pointer;
}
}
@include b(layout-content) {
width: 100%;
padding-left: getCssVar('layout-nav', 'width');
background-color: getCssVar('layout-content', 'bg-color');
transition: all 0.2s ease-in-out;
@include e(main) {
height: calc(100% - getCssVar('layout-header', 'height'));
padding-top: getCssVar('layout-header', 'height');
}
}
@include b(control-layout) {
width: 100%;
height: 100%;
}
\ No newline at end of file
@include b(view-layout) {
@include set-component-css-var('view-layout', $view-layout);
@include set-component-css-var('view-layout-header', $view-layout-header);
@include set-component-css-var('view-layout-content', $view-layout-content);
@include flex(column);
position: relative;
width: 100%;
height: 100%;
@include when(no-header) {
> .#{bem(view-layout-content)} {
margin: 0;
}
}
@include b(view-layout-header) {
flex-shrink: 0;
width: 100%;
height: getCssVar('view-layout', 'header-height');
background: getCssVar('view-layout', 'header-bg-color');
border-bottom: getCssVar('view-layout', 'header-border-bottom');
@include b(view-layout-header-content) {
@include flex(row, space-between);
@include e(right) {
@include flex(row, space-between);
}
@include e(caption) {
@include utils-ellipsis;
@include flex(row, flex-start, center);
max-width: getCssVar('view-layout-header', 'caption-max-width');
height: 100%;
font-size: getCssVar('view-layout-header', 'caption-font-size');
font-weight: getCssVar('view-layout-header', 'caption-font-weight');
color: getCssVar('view-layout-header', 'caption-color');
> img {
display: inline-block;
margin-right: 10px;
vertical-align: middle;
}
}
}
}
@include b(view-layout-content) {
@include flex;
flex: 1 1 auto;
min-height: getCssVar('view-layout-content', 'min-height');
overflow: auto;
background-color: getCssVar('view-layout-content', 'bg-color');
border-radius: getCssVar('view-layout-content', 'border-radius');
@include e(body) {
flex: 1 1 auto;
overflow: auto;
background-color: getCssVar('view-layout-content', 'body-bg-color');
}
}
// 路由模式下
@include m(route) {
@include b(view-layout-header) {
padding: getCssVar('view-layout', 'header-padding');
}
@include b(view-layout-content) {
padding: getCssVar('view-layout-content', 'padding');
margin: getCssVar('view-layout-content', 'margin');
}
}
// 抽屉
@include m(drawer) {
@include set-component-css-var(
'view-layout-drawer-header',
$view-layout-drawer-header
);
@include set-component-css-var(
'view-layout-drawer-content',
$view-layout-drawer-content
);
@include b(view-layout-header) {
padding: getCssVar('view-layout-drawer-header', 'padding');
}
@include b(view-layout-content) {
padding: getCssVar('view-layout-drawer-content', 'padding');
}
}
// 模态
@include m(modal) {
@include set-component-css-var(
'view-layout-modal-header',
$view-layout-modal-header
);
@include set-component-css-var(
'view-layout-modal-content',
$view-layout-modal-content
);
@include b(view-layout-header) {
padding: getCssVar('view-layout-modal-header', 'padding');
}
@include b(view-layout-content) {
padding: getCssVar('view-layout-modal-content', 'padding');
}
}
// 嵌入
@include m(embed) {
@include set-component-css-var(
'view-layout-embed-header',
$view-layout-embed-header
);
@include set-component-css-var(
'view-layout-embed-content',
$view-layout-embed-content
);
@include b(view-layout-header) {
height: getCssVar('view-layout-embed-header', 'height');
padding: getCssVar('view-layout-embed-header', 'padding');
line-height: getCssVar('view-layout-embed-header', 'line-height');
@include b(view-layout-header-content) {
height: getCssVar('view-layout-embed-header', 'content-height');
@include e(caption) {
font-size: getCssVar('view-layout-embed-header', 'caption-font-size');
color: getCssVar('view-layout-embed-header', 'caption-color');
}
}
}
@include b(view-layout-content) {
padding: getCssVar('view-layout-embed-content', 'padding');
margin: getCssVar('view-layout-embed-content', 'margin');
}
}
}
@include b(drawer) {
@include set-component-css-var('drawer', $drawer);
// 覆盖iView计算的z-index,使用变量
.ivu-drawer-mask,
.ivu-drawer-wrap {
z-index: getCssVar('drawer', 'z-index') !important;
}
.ivu-drawer-content {
@include flex(column);
height: 100%;
.ivu-drawer-body {
padding: 0;
}
}
}
@include b(modal) {
@include set-component-css-var('modal', $modal);
// 覆盖iView计算的z-index,使用变量
.ivu-modal-mask,
.ivu-modal-wrap {
z-index: getCssVar('modal', 'z-index') !important;
}
.ivu-modal-content {
@include flex(column);
height: 100%;
.ivu-modal-body {
flex-grow: 1;
padding: 0;
}
}
}
@include b(login-view) {
display: flex;
justify-content: center;
align-items: flex-start;
width: 100vw;
height: 100vh;
background: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
}
@media screen and (max-width: 720px) {
@include b(login-view) {
align-items: center;
}
}
@include b(login-view-box) {
width: 430px;
margin-top: 130px;
background: #fff;
overflow: hidden;
}
@media screen and (max-width: 720px) {
@include b(login-view-box) {
width: 340px;
margin-top: 0;
}
}
@include b(login-view-box-header) {
background: #ccccff;
> img {
display: block;
width: 430px;
margin: 0 auto;
user-select: none;
}
}
@include b(login-view-box-main) {
position: relative;
@include e(avatar) {
display: block;
position: absolute;
height: 100px;
width: 100px;
border-radius: 50%;
border: 4px solid #fff;
top: -50px;
right: 175px;
z-index: 2;
user-select: none;
}
}
@include b(login-view-box-main-content) {
padding: 100px 40px 40px;
.ivu-input {
outline: none;
border-color: #dcdfe6;
font-size: 14px;
&:hover {
border-color: #c0c4cc;
}
&:focus {
border-color: #409eff;
box-shadow: none;
}
}
.ivu-form-item-error .ivu-input {
border-color: #f56c6c;
}
.ivu-form-item-error-tip {
padding-top: 4px;
font-size: 12px;
color: #f56c6c;
}
.ivu-btn {
display: block;
height: 40px;
margin-top: 15px;
color: #fff;
background: #409eff;
border-color: #409eff;
&:focus {
background: #79bbff;
border-color: #79bbff;
box-shadow: none;
}
&:hover {
background: #79bbff;
border-color: #79bbff;
}
&:active {
background: #337ecc;
border-color: #337ecc;
}
}
.ivu-input-wrapper-large {
.ivu-input-icon,
.ivu-input-prefix i,
.ivu-input-suffix i {
font-size: 16px;
}
}
.ivu-form-item {
margin-bottom: 22px;
}
}
@include b(view-dempickupview-content) {
display: flex;
height: 100%;
}
@include b(view-dempickupview-left) {
flex-grow: 1;
}
@include b(view-dempickupview-center) {
@include set-component-css-var('mpickup-view-center', $mpickup-view-center);
@include flex(column, center);
flex: 0 0 getCssVar('mpickup-view-center', 'width');
background-color: getCssVar('mpickup-view-center', 'bg-color');
}
@include b(view-dempickupview-right) {
@include set-component-css-var('mpickup-view-right', $mpickup-view-right);
flex: 0 0 getCssVar('mpickup-view-right', 'width');
padding: getCssVar('mpickup-view-right', 'padding');
overflow: auto;
background-color: getCssVar('mpickup-view-right', 'bg-color');
@include e(list-item) {
@include flex(row, space-between);
padding: getCssVar('mpickup-view-right', 'item-padding');
border-bottom: getCssVar('mpickup-view-right', 'item-border-bottom');
> i {
cursor: pointer;
}
@include when(selected) {
color: getCssVar('mpickup-view-right', 'item-selected-color');
background-color: getCssVar(
'mpickup-view-right',
'item-selected-bg-color'
);
}
}
}
@include b(view-dempickupview-footer) {
@include set-component-css-var('mpickup-view-footer', $mpickup-view-footer);
@include flex(row, flex-end);
padding: getCssVar('mpickup-view-footer', 'padding');
border-top: getCssVar('mpickup-view-footer', 'border-top');
.ivu-btn:nth-child(1) {
color: getCssVar('mpickup-view-footer', 'confirm-btn-color');
background-color: getCssVar('mpickup-view-footer', 'confirm-btn-bg-color');
}
.ivu-btn:nth-child(2) {
margin: getCssVar('mpickup-view-footer', 'btn-margin');
}
}
@include b(view-deoptview-footer) {
@include set-component-css-var('opt-view-footer', $opt-view-footer);
@include flex(row, flex-end);
padding: getCssVar('opt-view-footer', 'padding');
border-top: getCssVar('opt-view-footer', 'border-top');
.ivu-btn:nth-child(1) {
color: getCssVar('opt-view-footer', 'confirm-btn-color');
background-color: getCssVar('opt-view-footer', 'confirm-btn-bg-color');
}
.ivu-btn:nth-child(2) {
margin: getCssVar('opt-view-footer', 'btn-margin');
}
}
@mixin menu-item-style {
@include flex(row, flex-start, center);
width: 100%;
height: getCssVar('app-menu', 'item-height');
padding: getCssVar('app-menu', 'item-padding');
font-size: getCssVar('app-menu', 'item-font-size');
color: getCssVar('app-menu', 'item-color');
white-space: nowrap;
&:hover {
color: getCssVar('app-menu', 'item-hover-color');
}
> .ivu-icon {
margin: 0;
}
}
@mixin menu-collapse-item-style {
@include flex-center;
padding: getCssVar('app-menu', 'collapse-item-padding');
&:hover {
color: getCssVar('app-menu', 'collapse-item-hover-color');
}
}
@mixin menu-item-selected-style {
color: getCssVar('app-menu', 'item-selected-color');
background-color: getCssVar('app-menu', 'item-selected-bg-color');
}
@include b(app-menu) {
position: static;
height: calc(100vh - getCssVar('layout', 'header-height'));
overflow-y: auto;
@include set-component-css-var('app-menu', $app-menu);
// 图标样式
@include e(icon) {
width: getCssVar('app-menu', 'icon-width');
height: getCssVar('app-menu', 'icon-height');
margin: getCssVar('app-menu', 'icon-margin');
}
// 收缩
@include when(collapse) {
@include e(item) {
@include menu-collapse-item-style;
}
@include b(app-menu-tooltip) {
width: 100%;
}
@include b(app-menu-submenu) {
display: block;
@include e(title) {
@include flex-center;
@include menu-collapse-item-style;
white-space: nowrap;
cursor: pointer;
}
@include e(item) {
padding: 0;
}
}
// 收缩菜单项激活样式 覆盖iview自带
.ivu-menu-item-active:not(.ivu-menu-submenu) {
@include menu-item-selected-style;
}
}
&.ivu-menu-vertical.ivu-menu-light::after {
display: none;
}
// 菜单默认样式 覆盖iview自带,自带给的900会高于模态等
.ivu-menu {
z-index: 5;
}
// 菜单项默认样式 覆盖iview自带
.ivu-menu-item,
.ivu-menu-submenu-title {
@include menu-item-style;
}
// 未收缩菜单项激活样式 覆盖iview自带
.ivu-menu-light.ivu-menu-vertical
.ivu-menu-item-active:not(.ivu-menu-submenu) {
@include menu-item-selected-style;
}
&::-webkit-scrollbar {
width: getCssVar('app-menu', 'scrollbar-width');
height: 0;
}
&::-webkit-scrollbar-thumb {
background: getCssVar('app-menu', 'scrollbar-bg-color');
}
}
@include b(form-button) {
width: 100%;
height: 100%;
}
@include b(form-druipart) {
@include set-component-css-var('form-druipart', $form-druipart);
position: relative;
width: 100%;
height: 100%;
@include e(mask) {
width: getCssVar('form-druipart', 'mask-width');
height: getCssVar('form-druipart', 'mask-height');
@include flex(row, center, center);
@include mask(rgba($color: #000, $alpha: 60%));
}
}
@include b(form-group) {
@include set-component-css-var('form-group', $form-group);
@include set-component-css-var('form-group-header', $form-group-header);
@include set-component-css-var('form-group-caption', $form-group-caption);
@include set-component-css-var('form-group-content', $form-group-content);
}
@include b(form-group) {
width: 100%;
height: 100%;
background-color: getCssVar('form-group', 'bg-color');
@include when(collapse) {
@include b(form-group-content) {
display: block;
}
}
@include e(caption) {
@include utils-ellipsis;
font-family: getCssVar('form-group-caption', 'font-family');
font-size: getCssVar('form-group-caption', 'font-size');
color: getCssVar('form-group-caption', 'text-color');
}
@include e(toolbar) {
text-align: right;
}
}
@include b(form-group-collapse) {
@include b(form-group-content) {
display: none;
}
}
@include b(form-group-header) {
padding: calc(getCssVar('form-group-header', 'padding') / 2);
border-bottom: #{getCssVar('border-width')} #{getCssVar('border-style')} #{getCssVar(
'border-color'
)};
@include e((left, right)) {
display: inline-block;
width: 50%;
}
@include e(right) {
text-align: right;
}
}
.#{bem('form-group', '', 'show-header')} {
> .#{bem('form-group-content')} {
padding: getCssVar('form-group-content', 'padding');
}
}
@include b(form-item-container) {
@include set-component-css-var('form-item-container', $form-item-container);
}
@include b(form-item-container) {
width: 100%;
height: 100%;
@include e(label) {
@include utils-ellipsis;
padding-top: calc(
(
#{getCssVar('form-item-container', 'line-height')} - #{getCssVar(
'form-item',
'font-size'
)}
) / 2
);
padding-bottom: calc(
(
#{getCssVar('form-item-container', 'line-height')} - #{getCssVar(
'form-item',
'font-size'
)}
) / 2
);
line-height: #{getCssVar('form-item', 'font-size')};
}
padding-right: getCssVar('padding-right');
padding-bottom: getCssVar('padding-bottom');
@include m((left, right)) {
min-height: #{getCssVar('form-item-container', 'line-height')};
line-height: #{getCssVar('form-item-container', 'line-height')};
@include e(label) {
flex-shrink: 0;
width: #{getCssVar('form-item-container', 'label-width')};
}
@include e(editor) {
flex-grow: 1;
}
}
@include m(left) {
@include e(label) {
padding-right: 10px;
text-align: right;
}
}
@include m(right) {
@include e(label) {
padding-left: 10px;
text-align: left;
}
}
@include when(required) {
@include e(label) {
&::before {
display: inline-block;
margin-right: 4px;
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
font-family: SimSun;
font-size: 14px;
line-height: 1;
color: #ed4014;
content: '*';
}
}
}
}
.#{bem('form-item-container', '', 'left')},
.#{bem('form-item-container', '', 'right')} {
.#{bem('form-item-container-content')} {
@include flex;
}
}
@include b(form-item-container-error) {
padding-top: 6px;
padding-left: #{getCssVar('form-item-container', 'label-width')};
color: #{getCssVar('form-item', 'error-color')};
}
@include b(form-item) {
@include set-component-css-var('form-item', $form-item);
@include set-component-css-var('form-item-caption', $form-item-caption);
}
\ No newline at end of file
@include b(form-page) {
@include set-component-css-var('form-page', $form-page);
.ivu-tabs-bar {
margin: 0;
}
}
@include b(form-page-item) {
padding-top: getCssVar('padding-top');
}
@include b(form-page-item-child) {
margin-bottom: 16px;
}
\ No newline at end of file
@include b(form-raw-item) {
width: 100%;
height: 100%;
}
@include b(form) {
@include set-component-css-var('form', $form);
}
@include b(form) {
width: 100%;
font-size: #{getCssVar('form', 'font-size')};
color: #{getCssVar('form', 'text-color')};
background-color: #{getCssVar('form', 'bg-color')};
}
@include b(grid-ua-column) {
@include set-component-css-var('grid-ua-column', $grid-ua-column);
.ivu-btn {
background-color: getCssVar('grid-ua-column', 'btn-bg-color');
}
}
@include b(grid) {
@include set-component-css-var('grid', $grid);
@include set-component-css-var('grid-header', $grid-header);
@include set-component-css-var('grid-content', $grid-content);
@include set-component-css-var('grid-page', $grid-page);
height: 100%;
padding: getCssVar('grid', 'padding');
background-color: getCssVar('grid', 'bg-color');
@include b(grid-content) {
height: calc(100% - getCssVar('grid-page', 'height'));
.ivu-table {
// grid header
.ivu-table-header {
height: getCssVar('grid-header', 'height');
overflow: hidden;
> table {
height: 100%;
color: getCssVar('grid-header', 'text-color');
.ivu-table-cell {
width: 100%;
@include utils-ellipsis;
}
}
}
// grid content
.ivu-table-body {
height: calc(100% - getCssVar('grid-header', 'height'));
overflow-y: auto;
> table {
color: getCssVar('grid-content', 'text-color');
// 高亮
.ivu-table-row-highlight td {
background-color: getCssVar(
'grid-content',
'item-highlight-bg-color'
);
}
// 悬浮
.ivu-table-row-hover td {
background-color: getCssVar('grid-content', 'item-hover-bg-color');
}
}
}
&::before {
z-index: auto;
}
}
}
@include b(grid-page) {
height: getCssVar('grid-page', 'height');
@include flex(row, flex-end);
padding: getCssVar('grid-page', 'padding');
}
}
/** 定义网站 HTML 元素的样式(如 H1 标签默认样式等),或者基础样式 **/
html,
body {
overflow: hidden;
}
.ibiz-app {
width: 100vw;
height: 100vh;
}
// 滚动条
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-thumb {
background: rgb(0 0 0 / 10%);
}
/** 重置标准化样式、框大小定义等,例如 normalize.css、reset.css **/
@use './settings/config.scss' as *;
@forward './settings/config';
@use './tools/function.scss' as *;
@forward './tools/function';
@use './tools/utils.scss' as *;
@forward './tools/utils';
@use './tools/mixin.scss' as *;
@forward './tools/mixin';
@use './settings/var.scss' as *;
@forward './settings/var';
@use './tools/css-var.scss' as *;
@forward './tools/css-var';
/* 重置标准样式 */
@import './generic/index';
/* 标准元素样式 */
@import './elements/index';
/* 面向对象编写样式 */
@import './objects/index';
/* 特异性最高的样式,唯一可以使用 !important */
@import './tumps/index';
/* 默认主题 */
@import './theme/index';
\ No newline at end of file
/** 面向对象编写样式, **/
@import './state/state';
\ No newline at end of file
// 状态
\ No newline at end of file
/* stylelint-disable annotation-no-unknown */
$namespace: 'ibiz' !default;
$common-separator: '-' !default;
$element-separator: '__' !default;
$modifier-separator: '--' !default;
$state-prefix: 'is-' !default;
\ No newline at end of file
/* Element Chalk Variables */
@use 'sass:math';
@use 'sass:map';
@use 'sass:color';
@use '../tools/function.scss' as *;
// types
$types: primary, success, warning, danger, error, info;
// Color
$colors: () !default;
$colors: map.deep-merge(
(
'white': #fff,
'black': #000,
'primary': (
'base': #409eff,
),
'success': (
'base': #67c23a,
),
'warning': (
'base': #e6a23c,
),
'danger': (
'base': #f56c6c,
),
'error': (
'base': #f56c6c,
),
'info': (
'base': #909399,
),
),
$colors
);
$color-white: map.get($colors, 'white') !default;
$color-black: map.get($colors, 'black') !default;
$color-primary: map.get($colors, 'primary', 'base') !default;
$color-success: map.get($colors, 'success', 'base') !default;
$color-warning: map.get($colors, 'warning', 'base') !default;
$color-danger: map.get($colors, 'danger', 'base') !default;
$color-error: map.get($colors, 'error', 'base') !default;
$color-info: map.get($colors, 'info', 'base') !default;
// https://sass-lang.com/documentation/values/maps#immutability
// mix colors with white/black to generate light/dark level
@mixin set-color-mix-level(
$type,
$number,
$mode: 'light',
$mix-color: $color-white
) {
$colors: map.deep-merge(
(
$type: (
'#{$mode}-#{$number}':
color.mix(
$mix-color,
map.get($colors, $type, 'base'),
math.percentage(math.div($number, 10))
),
),
),
$colors
) !global;
}
// $colors.primary.light-i
// --el-color-primary-light-i
// 10% 53a8ff
// 20% 66b1ff
// 30% 79bbff
// 40% 8cc5ff
// 50% a0cfff
// 60% b3d8ff
// 70% c6e2ff
// 80% d9ecff
// 90% ecf5ff
@each $type in $types {
@for $i from 1 through 9 {
@include set-color-mix-level($type, $i, 'light', $color-white);
}
}
// --el-color-primary-dark-2
@each $type in $types {
@include set-color-mix-level($type, 2, 'dark', $color-black);
}
$padding: () !default;
$padding: map.merge(
(
'': 16px,
'left': 16px,
'right': 16px,
'top': 16px,
'bottom': 16px,
),
$padding
);
$text-color: () !default;
$text-color: map.merge(
(
'': #303133,
'primary': #303133,
'regular': #606266,
'secondary': #909399,
'placeholder': #a8abb2,
'disabled': #c0c4cc,
),
$text-color
);
$border-color: () !default;
$border-color: map.merge(
(
'': #dcdfe6,
'light': #e4e7ed,
'lighter': #ebeef5,
'extra-light': #f2f6fc,
'dark': #d4d7de,
'darker': #cdd0d6,
),
$border-color
);
$fill-color: () !default;
$fill-color: map.merge(
(
'': #f0f2f5,
'light': #f5f7fa,
'lighter': #fafafa,
'extra-light': #fafcff,
'dark': #ebedf0,
'darker': #e6e8eb,
'blank': #fff,
),
$fill-color
);
// Background
$bg-color: () !default;
$bg-color: map.merge(
(
'': #fff,
'page': #f5f7f9,
'overlay': #fff,
),
$bg-color
);
// Border
$border-width: 1px !default;
$border-style: solid !default;
$border-color-hover: getCssVar('text-color', 'disabled') !default;
$border-radius: () !default;
$border-radius: map.merge(
(
'base': 4px,
'small': 2px,
'round': 20px,
'circle': 100%,
),
$border-radius
);
// Box-shadow
$box-shadow: () !default;
$box-shadow: map.merge(
(
'': (
0 12px 32px 4px rgb(0 0 0 / 4%),
0 8px 20px rgb(0 0 0 / 8%),
),
'light': (
0 0 12px rgb(0 0 0 / 12%),
),
'lighter': (
0 0 6px rgb(0 0 0 / 12%),
),
'dark': (
0 16px 48px 16px rgb(0 0 0 / 8%),
0 12px 32px rgb(0 0 0 / 12%),
0 8px 16px -8px rgb(0 0 0 / 16%),
),
),
$box-shadow
);
// Typography
$font-family: () !default;
$font-family: map.merge(
(
// default family
'':
"'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif"
),
$font-family
);
$font-size: () !default;
$font-size: map.merge(
(
'extra-large': 20px,
'large': 18px,
'medium': 16px,
'base': 14px,
'small': 13px,
'extra-small': 12px,
),
$font-size
);
// zIndex
$z-index: () !default;
$z-index: map.merge(
(
'normal': 1,
'top': 1000,
'popper': 2000,
),
$z-index
);
// Disable default
$disabled: () !default;
$disabled: map.merge(
(
'bg-color': getCssVar('fill-color', 'light'),
'text-color': getCssVar('text-color', 'placeholder'),
'border-color': getCssVar('border-color', 'light'),
),
$disabled
);
$common-component-size: () !default;
$common-component-size: map.merge(
(
'large': 40px,
'default': 32px,
'small': 24px,
),
$common-component-size
);
// overlay
$overlay-color: () !default;
$overlay-color: map.merge(
(
'': rgb(0 0 0 / 80%),
'light': rgb(0 0 0 / 70%),
'lighter': rgb(0 0 0 / 50%),
),
$overlay-color
);
// mask
$mask-color: () !default;
$mask-color: map.merge(
(
'': rgb(255 255 255 / 90%),
'extra-light': rgb(255 255 255 / 30%),
),
$mask-color
);
// components
$skeleton-color: rgba(190, 190, 190, 0.2); // 骨架屏展示颜色
$skeleton-transition-color: rgba(129, 129, 129, 0.24); // 骨架屏动画效果过渡颜色
/* 应用布局 start */
$app-layout: () !default;
$app-layout: map.merge(
(
'bg-color': getCssVar('bg-color-page'),
'text-color': #fff,
'caption-font-size': 18px,
),
$app-layout
);
$app-layout-nav: () !default;
$app-layout-nav: map.merge(
(
'bg-color': #fff,
'width': 256px,
'width-collapse': 80px,
'box-shadow': 2px 0 8px 0 rgb(29 35 41 / 5%),
),
$app-layout-nav
);
$app-layout-header: () !default;
$app-layout-header: map.merge(
(
'height': 64px,
'bg-color': #191a23,
'text-color': getCssVar('layout', 'text-color'),
'box-shadow': 0 1px 4px rgb(0 21 41 / 8%),
'icon-font-size': 20px,
'back-icon-font-size': 20px,
'back-icon-margin': 0 0 0 20px,
),
$app-layout-header
);
$app-layout-content: () !default;
$app-layout-content: map.merge(
(
'bg-color': getCssVar('layout', 'bg-color'),
),
$app-layout-content
);
/* 应用布局 end */
/* view-toolbar 视图工具栏 start */
$view-toolbar: () !default;
$view-toolbar: map.merge(
(
'item-margin': 0 4px,
'icon-margin': 0 5px 0 0,
'icon-max-width': 16px,
'icon-max-height': 16px,
),
$view-toolbar
);
$view-toolbar-embed: () !default;
$view-toolbar-embed: map.merge(
(
'item-height': 21px,
'item-line-height': 21px,
),
$view-toolbar-embed
);
/* view-toolbar 视图工具栏 end */
/* app-menu 菜单 start */
$app-menu: () !default;
$app-menu: map.merge(
(
'item-selected-color': #2d8cf0,
'item-selected-bg-color': #f0faff,
'item-hover-color': #2d8cf0,
'item-padding': 14px 24px,
'item-font-size': 14px,
'item-height': 52px,
'item-color': #515a6e,
'collapse-item-hover-color': #2d8cf0,
'collapse-item-padding': 14px 24px,
'icon-width': 20px,
'icon-height': 20px,
'icon-margin': 0 5px 0 0,
'scrollbar-width': 4px,
'scrollbar-bg-color': rgb(0 0 0 / 10%),
),
$app-menu
);
/* app-menu 菜单 end */
/* 表单 start */
$form: () !default;
$form: map.merge(
(
'text-color': getCssVar('text-color', 'primary'),
'font-size': getCssVar('font-size', 'base'),
'bg-color': transparent,
),
$form
);
$form-page: () !default;
$form-page: map.merge(
(
'caption-color': getCssVar('text-color', 'primary'),
'caption-font-size': getCssVar('font-size', 'medium'),
'caption-bg-color': #2d8cf0,
'caption-hover-color': #0879f3,
),
$form-page
);
$form-group: () !default;
$form-group: map.merge(
(
'bg-color': getCssVar('bg-color', 'overlay'),
),
$form-group
);
$form-group-header: () !default;
$form-group-header: map.merge(
(
'bg-color': getCssVar('bg-color', 'overlay'),
'padding': getCssVar('padding'),
),
$form-group
);
$form-group-caption: () !default;
$form-group-caption: map.merge(
(
'color': getCssVar('text-color', 'primary'),
'font-size': getCssVar('font-size', 'medium'),
'font-style': getCssVar('font-family'),
),
$form-group
);
$form-group-content: () !default;
$form-group-content: map.merge(
(
'padding': getCssVar('padding'),
),
$form-group
);
$form-item: () !default;
$form-item: map.merge(
(
'color': getCssVar('text-color', 'primary'),
'hover-color': getCssVar('color', 'primary'),
'border-color': getCssVar('border-color'),
'font-size': getCssVar('font-size', 'base'),
'font-family': getCssVar('font-size', 'base'),
'error-color': getCssVar('color', 'error'),
'placeholder-color': getCssVar('text-color', 'placeholder'),
'disable-color': getCssVar('text-color', 'secondary'),
'disable-bg-color': #f5f5f5,
'disable-border-color': #e4e4e4,
'line-height': 32px,
),
$form-item
);
$form-item-caption: () !default;
$form-item-caption: map.merge(
(
'color': getCssVar('form-item', 'text-color', 'primary'),
'font-size': getCssVar('form-item', 'font-size', 'base'),
'font-family': getCssVar('form-item', 'font-family'),
),
$form-item-caption
);
$form-item-container: () !default;
$form-item-container: map.merge(
(
'label-width': '130px',
'line-height': 32px,
),
$form-item-container
);
$form-druipart: () !default;
$form-druipart: map.merge(
(
'mask-width': 100%,
'mask-height': 100%,
),
$form-druipart
);
/* 表单 end */
/* 暂时保留,后期修改 End */
/* 视图布局 start */
$view-layout: () !default;
$view-layout: map.merge(
(
'text-color': getCssVar('text-color', 'primary'),
'bg-color': getCssVar('bg-color', 'page'),
),
$view-layout
);
$view-layout-header: () !default;
$view-layout-header: map.merge(
(
'height': 60px,
'padding': 16px 32px 0,
'bg-color': #fff,
'border-bottom': 1px solid #e8eaec,
'caption-max-width': 300px,
'caption-font-weight': 500,
'caption-font-size': getCssVar('font-size', 'extra-large'),
'caption-color': getCssVar('text-color', 'primary'),
),
$view-layout-header
);
$view-layout-content: () !default;
$view-layout-content: map.merge(
(
'text-color': getCssVar('view-layout', 'text-color'),
'bg-color': getCssVar('view-layout', 'bg-color'),
'padding': 0 0 0 24px,
'margin': 16px 0 0,
'border-radius': 4px,
'min-height': 300px,
'body-bg-color': #fff,
),
$view-layout-content
);
// 抽屉
$view-layout-drawer-header: () !default;
$view-layout-drawer-header: map.merge(
(
'padding': 16px 32px 0,
),
$view-layout-drawer-header
);
$view-layout-drawer-content: () !default;
$view-layout-drawer-content: map.merge(
(
'padding': 0,
),
$view-layout-drawer-content
);
// 模态
$view-layout-modal-header: () !default;
$view-layout-modal-header: map.merge(
(
'padding': 16px 32px 0,
),
$view-layout-modal-header
);
$view-layout-modal-content: () !default;
$view-layout-modal-content: map.merge(
(
'padding': 0,
),
$view-layout-modal-content
);
// 嵌入
$view-layout-embed-header: () !default;
$view-layout-embed-header: map.merge(
(
'padding': 8px,
'height': 38px,
'line-height': 21px,
'caption-font-size': getCssVar('font-size', 'base'),
'caption-color': getCssVar('text-color', 'primary'),
'content-height': 21px,
),
$view-layout-embed-header
);
$view-layout-embed-content: () !default;
$view-layout-embed-content: map.merge(
(
'padding': 0,
'margin': 0,
),
$view-layout-embed-content
);
/* 视图布局 end */
/* 表格 start */
$grid: () !default;
$grid: map.merge(
(
'text-color': getCssVar('text-color', 'primary'),
'bg-color': #fff,
'padding': getCssVar('padding'),
),
$grid
);
$grid-header: () !default;
$grid-header: map.merge(
(
'text-color': #515a6e,
'height': 45px,
),
$grid-header
);
$grid-content: () !default;
$grid-content: map.merge(
(
'text-color': getCssVar('grid', 'text-color'),
'item-highlight-bg-color': #ebf7ff,
'item-hover-bg-color': #ebf7ff,
),
$grid-content
);
$grid-page: () !default;
$grid-page: map.merge(
(
'text-color': getCssVar('grid', 'text-color'),
'height': 50px,
'padding': 16px 0 0 0,
),
$grid-page
);
$grid-ua-column: () !default;
$grid-ua-column: map.merge(
(
'btn-bg-color': transparent,
),
$grid-ua-column
);
/* 表格 end */
/* 模态 start */
$modal: () !default;
$modal: map.merge(
(
'z-index': 500,
),
$modal
);
/* 模态 end */
/* 抽屉 start */
$drawer: () !default;
$drawer: map.merge(
(
'z-index': 500,
),
$drawer
);
/* 抽屉 end */
/* 多项选择视图 start */
$mpickup-view-center: () !default;
$mpickup-view-center: map.merge(
(
'width': 50px,
'bg-color': #f5f7f9,
),
$mpickup-view-center
);
$mpickup-view-right: () !default;
$mpickup-view-right: map.merge(
(
'width': 200px,
'padding': 50px 0 0,
'bg-color': #fff,
'item-padding': 10px,
'item-border-bottom': 1px solid #e8eaec,
'item-selected-bg-color': #2d8cf0,
'item-selected-color': #fff,
),
$mpickup-view-right
);
$mpickup-view-footer: () !default;
$mpickup-view-footer: map.merge(
(
'padding': 12px 16px,
'border-top': 1px solid #e8eaec,
'btn-margin': 0 0 0 10px,
'confirm-btn-bg-color': #2e8cf0,
'confirm-btn-color': #fff,
),
$mpickup-view-footer
);
/* 多项选择 end */
/* 选项操作视图 start */
$opt-view-footer: () !default;
$opt-view-footer: map.merge(
(
'padding': 12px 16px,
'border-top': 1px solid #e8eaec,
'btn-margin': 0 0 0 10px,
'confirm-btn-bg-color': #2e8cf0,
'confirm-btn-color': #fff,
),
$opt-view-footer
);
/* 选项操作视图 end */
/* 快速搜索 start */
$quick-search-embed: () !default;
$quick-search-embed: map.merge(
(
'height': 21px,
'line-height': 21px,
),
$quick-search-embed
);
/* 快速搜索 end */
@use './var.scss' as *;
html.dark {
color-scheme: dark;
// hex colors
@each $type in (primary, success, warning, danger, error, info) {
@include set-css-color-type($colors, $type);
}
// --ibiz-box-shadow-#{$type}
@include set-component-css-var('box-shadow', $box-shadow);
// color-scheme
// Background --ibiz-bg-color-#{$type}
@include set-component-css-var('bg-color', $bg-color);
// --ibiz-text-color-#{$type}
@include set-component-css-var('text-color', $text-color);
// --ibiz-border-color-#{$type}
@include set-component-css-var('border-color', $border-color);
// Fill --ibiz-fill-color-#{$type}
@include set-component-css-var('fill-color', $fill-color);
@include set-component-css-var('mask-color', $mask-color);
}
@include dark(button) {
@include set-component-css-var('button', $button);
}
@include dark(card) {
@include set-component-css-var('card', $card);
}
@include dark(empty) {
@include set-component-css-var('empty', $empty);
}
\ No newline at end of file
@use 'sass:map';
@use 'sass:math';
@use 'sass:color';
@use '../../settings/var.scss' as common;
@use '../../tools/function.scss' as *;
$colors: () !default;
@each $type in common.$types {
$colors: map.deep-merge(
(
$type: (
'base': map.get(common.$colors, $type, 'base'),
),
),
$colors
) !global;
}
// https://sass-lang.com/documentation/values/maps#immutability
// mix colors with white/black to generate light/dark level
@mixin set-color-mix-level(
$type,
$number,
$mode: 'light',
$mix-color: $color-white
) {
// hex mix color
$colors: map.deep-merge(
(
$type: (
'#{$mode}-#{$number}':
color.mix(
$mix-color,
map.get($colors, $type, 'base'),
math.percentage(math.div($number, 10))
),
),
),
$colors
) !global;
// for designer to view transparent
// $colors: map.deep-merge(
// (
// $type: (
// '#{$mode}-#{$number}':
// rgba(map.get($colors, $type, 'base'), math.div(10 - $number, 10)),
// ),
// ),
// $colors
// ) !global;
}
// Background
$bg-color: () !default;
$bg-color: map.merge(
(
'': #141414,
'page': #0a0a0a,
'overlay': #1d1e1f,
),
$bg-color
);
// dark-mode
@each $type in common.$types {
@for $i from 1 through 9 {
@include set-color-mix-level($type, $i, 'light', map.get($bg-color, ''));
}
}
@each $type in common.$types {
@include set-color-mix-level($type, 2, 'dark', common.$color-white);
}
// border
$border-color-base: #f5f8ff;
$border-color: () !default;
$border-color: map.merge(
(
'darker': rgba($border-color-base, 0.35),
'dark': rgba($border-color-base, 0.3),
'': rgba($border-color-base, 0.25),
'light': rgba($border-color-base, 0.2),
'lighter': rgba($border-color-base, 0.15),
'extra-light': rgba($border-color-base, 0.1),
),
$border-color
);
// mix to hex to avoid overlay issues
@each $key, $val in $border-color {
$border-color: map.merge(
$border-color,
(
$key: mix-overlay-color($val, map.get($bg-color, '')),
)
) !global;
}
// Box-shadow
$box-shadow: () !default;
$box-shadow: map.merge(
(
'': (
0 12px 32px 4px rgb(0 0 0 / 36%),
0 8px 20px rgb(0 0 0 / 72%),
),
'light': (
0 0 12px rgb(0 0 0 / 72%),
),
'lighter': (
0 0 6px rgb(0 0 0 / 72%),
),
'dark': (
0 16px 48px 16px rgb(0 0 0 / 72%),
0 12px 32px #000,
0 8px 16px -8px #000,
),
),
$box-shadow
);
// fill
$fill-color-base: #fafcff;
$fill-color: () !default;
$fill-color: map.merge(
(
'darker': rgba($fill-color-base, 0.2),
'dark': rgba($fill-color-base, 0.16),
'': rgba($fill-color-base, 0.12),
'light': rgba($fill-color-base, 0.08),
'lighter': rgba($fill-color-base, 0.04),
'extra-light': rgba($fill-color-base, 0.02),
'blank': transparent,
),
$fill-color
);
// mix to hex to avoid overlay issues
@each $key, $val in $fill-color {
@if $key != 'blank' {
$fill-color: map.merge(
$fill-color,
(
$key: mix-overlay-color($val, map.get($bg-color, '')),
)
) !global;
}
}
// text
$text-color-base: #f0f5ff;
$text-color: () !default;
$text-color: map.merge(
(
'primary': rgba($text-color-base, 0.95),
'regular': rgba($text-color-base, 0.85),
'secondary': rgba($text-color-base, 0.65),
'placeholder': rgba($text-color-base, 0.55),
'disabled': rgba($text-color-base, 0.4),
),
$text-color
);
// mix to hex to avoid overlay issues
@each $key, $val in $text-color {
$text-color: map.merge(
$text-color,
(
$key: mix-overlay-color($val, map.get($bg-color, '')),
)
) !global;
}
// mask
$mask-color: () !default;
$mask-color: map.merge(
(
'': rgb(0 0 0 / 80%),
'extra-light': rgb(0 0 0 / 30%),
),
$mask-color
);
// Button
// css3 var in packages/theme-chalk/src/button.scss
$button: () !default;
$button: map.merge(
(
'disabled-text-color': rgb(255 255 255 / 50%),
),
$button
);
// card
$card: () !default;
$card: map.merge(
(
'bg-color': getCssVar('bg-color', 'overlay'),
),
$card
);
// Empty
// css3 var in packages/theme-chalk/src/empty.scss
$empty: () !default;
$empty: map.merge(
(
'fill-color-0': getCssVar('color-black'),
'fill-color-1': #4b4b52,
'fill-color-2': #36383d,
'fill-color-3': #1e1e20,
'fill-color-4': #262629,
'fill-color-5': #202124,
'fill-color-6': #212224,
'fill-color-7': #1b1c1f,
'fill-color-8': #1c1d1f,
'fill-color-9': #18181a,
),
$empty
);
@import './light/light-theme';
@import './dark/dark-theme';
\ No newline at end of file
:root {
@include set-css-var-value('color-white', $color-white);
@include set-css-var-value('color-black', $color-black);
// get rgb
@each $type in (primary, success, warning, danger, error, info) {
@include set-css-color-rgb($type);
}
// Typography
@include set-component-css-var('font-size', $font-size);
@include set-component-css-var('font-family', $font-family);
@include set-component-css-var('padding', $padding);
@include set-css-var-value('font-weight-primary', 500);
@include set-css-var-value('font-line-height-primary', 24px);
// z-index --ibiz-index-#{$type}
@include set-component-css-var('index', $z-index);
// --ibiz-border-radius-#{$type}
@include set-component-css-var('border-radius', $border-radius);
}
// for light
:root {
color-scheme: light;
@include set-css-var-value('color-white', $color-white);
@include set-css-var-value('color-black', $color-black);
// --ibiz-color-#{$type}
// --ibiz-color-#{$type}-light-{$i}
@each $type in (primary, success, warning, danger, error, info) {
@include set-css-color-type($colors, $type);
}
// color-scheme
// Background --ibiz-bg-color-#{$type}
@include set-component-css-var('bg-color', $bg-color);
// --ibiz-text-color-#{$type}
@include set-component-css-var('text-color', $text-color);
// --ibiz-border-color-#{$type}
@include set-component-css-var('border-color', $border-color);
// Fill --ibiz-fill-color-#{$type}
@include set-component-css-var('fill-color', $fill-color);
// Box-shadow
// --ibiz-box-shadow-#{$type}
@include set-component-css-var('box-shadow', $box-shadow);
// Disable base
@include set-component-css-var('disabled', $disabled);
// overlay & mask
@include set-component-css-var('overlay-color', $overlay-color);
@include set-component-css-var('mask-color', $mask-color);
// Border
@include set-css-var-value('border-width', $border-width);
@include set-css-var-value('border-style', $border-style);
@include set-css-var-value('border-color-hover', $border-color-hover);
@include set-css-var-value(
'border',
getCssVar('border-width') getCssVar('border-style')
getCssVar('border-color')
);
}
\ No newline at end of file
@use 'sass:map';
@use 'function.scss' as *;
@use '../settings/var.scss' as *;
// set css var value, because we need translate value to string
// for example:
// @include set-css-var-value(('color', 'primary'), red);
// --ibiz-color-primary: red;
@mixin set-css-var-value($name, $value) {
#{joinVarName($name)}: #{$value};
}
// @include set-css-var-type('color', 'primary', $map);
// --ibiz-color-primary: #{map.get($map, 'primary')};
@mixin set-css-var-type($name, $type, $variables) {
#{getCssVarName($name, $type)}: #{map.get($variables, $type)};
}
@mixin set-css-color-type($colors, $type) {
@include set-css-var-value(('color', $type), map.get($colors, $type, 'base'));
@each $i in (3, 5, 7, 8, 9) {
@include set-css-var-value(
('color', $type, 'light', $i),
map.get($colors, $type, 'light-#{$i}')
);
}
}
// set all css var for component by map
@mixin set-component-css-var($name, $variables) {
@each $attribute, $value in $variables {
@if $attribute == 'default' {
#{getCssVarName($name)}: #{$value};
} @else {
#{getCssVarName($name, $attribute)}: #{$value};
}
}
}
@mixin set-css-color-rgb($type) {
$color: map.get($colors, $type, 'base');
@include set-css-var-value(
('color', $type, 'rgb'),
#{red($color),
green($color),
blue($color)}
);
}
// generate css var from existing css var
// for example:
// @include css-var-from-global(('button', 'text-color'), ('color', $type))
// --ibiz-button-text-color: var(--ibiz-color-#{$type});
@mixin css-var-from-global($var, $gVar) {
$varName: joinVarName($var);
$gVarName: joinVarName($gVar);
#{$varName}: var(#{$gVarName});
}
@use 'sass:meta';
@use 'sass:string';
@use '../settings/config.scss' as *;
// BEM support Func
@function selectorToString($selector) {
$selector: meta.inspect($selector);
$selector: string.slice($selector, 2, -2);
@return $selector;
}
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if string.index($selector, $modifier-separator) {
@return true;
} @else {
@return false;
}
}
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if string.index($selector, '.' + $state-prefix) {
@return true;
} @else {
@return false;
}
}
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if string.index($selector, ':') {
@return true;
} @else {
@return false;
}
}
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
// join var name
// joinVarName(('button', 'text-color')) => '--ibiz-button-text-color'
@function joinVarName($list) {
$name: '--' + $namespace;
@each $item in $list {
@if $item != '' {
$name: $name + '-' + $item;
}
}
@return $name;
}
// getCssVarName('button', 'text-color') => '--ibiz-button-text-color'
@function getCssVarName($args...) {
@return joinVarName($args);
}
// getCssVar('button', 'text-color') => var(--ibiz-button-text-color)
@function getCssVar($args...) {
@return var(#{joinVarName($args)});
}
// getCssVarWithDefault(('button', 'text-color'), red) => var(--ibiz-button-text-color, red)
@function getCssVarWithDefault($args, $default) {
@return var(#{joinVarName($args)}, #{$default});
}
/* bem('block', 'element', 'modifier') => 'ibiz-block__element--modifier' */
@function bem($block, $element: '', $modifier: '') {
$name: $namespace + $common-separator + $block;
@if $element != '' {
$name: $name + $element-separator + $element;
}
@if $modifier != '' {
$name: $name + $modifier-separator + $modifier;
}
@return $name;
}
@use '../settings/config.scss' as config;
@use './function.scss' as *;
// BEM
@mixin b($block) {
$B: config.$namespace + '-' + $block !global;
.#{$B} {
@content;
}
}
@mixin e($element) {
$E: $element !global;
$selector: &;
$currentSelector: '';
@each $unit in $element {
$currentSelector: #{$currentSelector + '.' + $B + config.$element-separator + $unit + ','};
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
@mixin m($modifier) {
$selector: &;
$currentSelector: '';
@each $unit in $modifier {
$currentSelector: #{$currentSelector + $selector + config.$modifier-separator + $unit + ','};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
@mixin when($state) {
@at-root {
&.#{config.$state-prefix + $state} {
@content;
}
}
}
// dark
@mixin dark($block) {
html.dark {
@include b($block) {
@content;
}
}
}
// 完全容器居中
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// 文字出省略号
@mixin utils-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// flex样式复用
@mixin flex(
$direction: row,
$horizontal: initial,
$vertical: initial,
$wrap: nowrap
) {
display: flex;
@if $direction != row {
flex-flow: $direction $wrap;
}
@if $wrap != nowrap {
flex-wrap: $wrap;
}
@if $vertical != initial {
align-items: $vertical;
}
@if $horizontal != initial {
justify-content: $horizontal;
}
}
// mask样式复用
@mixin mask($bg-color) {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
color: #fff;
background: $bg-color;
}
/** 特异性最高的样式,唯一可以使用 !important **/
\ No newline at end of file
declare module 'vue' {
export interface GlobalComponents {
AppLayout: typeof import('../components/layout')['AppLayout'];
ControlLayout: typeof import('../components/layout')['ControlLayout'];
ViewLayout: typeof import('../components/layout')['ViewLayout'];
AppIcon: typeof import('../components/common')['AppIcon'];
AppToolbar: typeof import('../components/common')['AppToolbar'];
QuickSearch: typeof import('../components/common')['QuickSearch'];
// 视图
IndexView: typeof import('../views')['IndexView'];
GridView: typeof import('../components/views')['GridView'];
EditView: typeof import('../components/views')['EditView'];
// 部件
AppMenu: typeof import('../components/widgets')['AppMenu'];
GridControl: typeof import('../components/widgets')['GridControl'];
FormControl: typeof import('../components/widgets')['FormControl'];
FormButton: typeof import('../components/widgets')['FormButton'];
FormDruipart: typeof import('../components/widgets')['FormDRUIPart'];
FormGroupPanel: typeof import('../components/widgets')['FormGroupPanel'];
AppFormItem: typeof import('../components/widgets')['FormItem'];
FormPage: typeof import('../components/widgets')['FormPage'];
FormRawItem: typeof import('../components/widgets')['FormRawItem'];
FormTabPage: typeof import('../components/widgets')['FormTabPage'];
FormTabPanel: typeof import('../components/widgets')['FormTabPanel'];
EditFormControl: typeof import('../components/widgets')['EditFormControl'];
SearchFormControl: typeof import('../components/widgets')['SearchFormControl'];
}
}
export {};
import VueRouter, { Route } from 'vue-router';
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter;
$route: Route;
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
router?: VueRouter;
beforeRouteEnter?: NavigationGuard<V>;
beforeRouteLeave?: NavigationGuard<V>;
beforeRouteUpdate?: NavigationGuard<V>;
}
}
import VueRouter, { Route } from 'vue-router';
declare module '*.vue' {
import Vue from 'vue';
interface Vue {
$router: VueRouter;
$route: Route;
}
export default Vue;
}
export { getViewComponentName } from './view/get-view-component';
import { ViewType } from '@ibiz-template/model';
/**
* 根据视图类型返回视图组件的名称
*
* @author lxm
* @date 2022-09-08 18:09:39
* @export
* @param {string} viewType
* @returns {*}
*/
export function getViewComponentName(viewType: string) {
// 确定视图组件
switch (viewType) {
case ViewType.APP_INDEX_VIEW:
return 'IndexView';
case ViewType.DE_GRID_VIEW:
case ViewType.DE_GRID_VIEW9:
return 'GridView';
case ViewType.DE_EDIT_VIEW:
return 'EditView';
case ViewType.DE_OPT_VIEW:
return 'OptView';
case ViewType.DE_PICKUP_VIEW:
return 'PickupView';
case ViewType.DE_MPICKUP_VIEW:
return 'MPickupView';
case ViewType.DE_PICKUP_GRID_VIEW:
return 'PickupGridView';
default:
return '';
}
}
import { HttpError, RuntimeError } from '@ibiz-template/core';
import { DefectModelError, UnsupportedModelError } from '@ibiz-template/model';
/**
* 事件处理工具
*
* @author lxm
* @date 2022-09-21 18:09:31
* @export
* @class ErrorHandler
*/
export class ErrorHandler {
/**
* 处理异常
*
* @author lxm
* @date 2022-09-21 18:09:00
* @static
* @param {Error} err
*/
static handlerError(err: Error) {
if (
err instanceof DefectModelError ||
err instanceof UnsupportedModelError
) {
ibiz.message.error(err.message, 10, true);
} else if (err instanceof HttpError) {
ibiz.notification.error({
title: '异常',
desc: err.message,
duration: 10,
});
} else if (err instanceof RuntimeError) {
ibiz.message.error(err.message);
} else {
console.error(err);
}
}
}
export { LoadingUtil } from './loading-util/loading-util';
export { MessageUtil } from './message-util/message-util';
export { ModalUtil } from './model-util/model-util';
export { NotificationUtil } from './notification-util/notification-util';
export { OpenViewUtil } from './open-view-util/open-view-util';
export { ErrorHandler } from './error-handler/error-handler';
export * from './component-name';
import { ILoadingUtil } from '@ibiz-template/runtime';
import Vue from 'vue';
/**
* 全局加载动画工具
*
* @author chitanda
* @date 2022-08-17 17:08:44
* @export
* @class LoadingUtil
* @implements {ILoadingUtil}
*/
export class LoadingUtil implements ILoadingUtil {
/**
* 当前只在触发的全局加载次数
*
* @author chitanda
* @date 2022-08-17 17:08:44
* @protected
*/
protected count = 0;
/**
* 显示全局加载动画
*
* @author chitanda
* @date 2022-08-17 17:08:41
* @param {string} [message='加载中...']
*/
show(): void {
if (this.count === 0) {
Vue.prototype.$Spin.show();
}
this.count += 1;
}
/**
* 隐藏全局加载动画
*
* @author chitanda
* @date 2022-08-17 17:08:11
*/
hide(): void {
if (this.count > 0) {
this.count -= 1;
}
if (this.count === 0) {
Vue.prototype.$Spin.hide();
}
}
}
import Vue from 'vue';
import { IMessageUtil } from '@ibiz-template/runtime';
import { Message } from 'view-design';
/**
* 消息通知
*
* @author chitanda
* @date 2022-08-17 16:08:24
* @export
* @class MessageUtil
* @implements {IMessageUtil}
*/
export class MessageUtil implements IMessageUtil {
protected util: Message = Vue.prototype.$Message;
info(
msg: string,
duration?: number | undefined,
closable?: boolean | undefined,
): void {
this.util.info({ content: msg, duration, closable });
}
success(
msg: string,
duration?: number | undefined,
closable?: boolean | undefined,
): void {
this.util.success({ content: msg, duration, closable });
}
warning(
msg: string,
duration?: number | undefined,
closable?: boolean | undefined,
): void {
this.util.warning({ content: msg, duration, closable });
}
error(
msg: string,
duration?: number | undefined,
closable?: boolean | undefined,
): void {
this.util.error({ content: msg, duration, closable });
}
}
import Vue from 'vue';
import { IModalUtil, ModalParams } from '@ibiz-template/runtime';
import { ModalInstance } from 'view-design';
/**
* 简洁确认操作框
*
* @author chitanda
* @date 2022-08-17 16:08:52
* @export
* @class ModalUtil
* @implements {IModalUtil}
*/
export class ModalUtil implements IModalUtil {
protected modal: ModalInstance = Vue.prototype.$Modal;
async info(params: ModalParams): Promise<void> {
this.modal.info(params);
}
async success(params: ModalParams): Promise<void> {
this.modal.success(params);
}
async warning(params: ModalParams): Promise<void> {
this.modal.warning(params);
}
async error(params: ModalParams): Promise<void> {
this.modal.error(params);
}
async confirm(params: ModalParams): Promise<boolean> {
return new Promise(resolve => {
this.modal.confirm({
...params,
onOk: () => {
resolve(true);
},
onCancel: () => {
resolve(false);
},
});
});
}
}
import Vue from 'vue';
import { INotificationUtil, NotificationParams } from '@ibiz-template/runtime';
import { Notice } from 'view-design';
/**
* 在界面右上角显示可关闭的全局通知
*
* @author chitanda
* @date 2022-08-17 16:08:26
* @export
* @class NotificationUtil
* @implements {INotificationUtil}
*/
export class NotificationUtil implements INotificationUtil {
protected notice: Notice = Vue.prototype.$Notice;
info(params: NotificationParams): void {
this.notice.info(params);
}
success(params: NotificationParams): void {
this.notice.success(params);
}
warning(params: NotificationParams): void {
this.notice.warning(params);
}
error(params: NotificationParams): void {
this.notice.error(params);
}
}
import {
defineComponent,
getCurrentInstance,
PropType,
reactive,
ref,
} from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/util/drawer/drawer.scss';
import { ViewNeuron } from '@ibiz-template/controller';
import { IModal, IModalData, ViewMode } from '@ibiz-template/runtime';
import { AppDrawerOptions } from '@/interface';
import { useUIStore } from '@/store';
export const AppDrawerComponent = defineComponent({
props: {
componentName: String,
componentProps: Object as PropType<IData>,
opts: {
type: Object as PropType<AppDrawerOptions>,
default: () => ({}),
},
resolve: { type: Function, required: true },
reject: { type: Function, required: true },
},
setup(props) {
const ns = useNamespace('drawer');
const isShow = ref(true);
const { proxy } = getCurrentInstance()!;
const { zIndex } = useUIStore();
const drawerZIndex = zIndex.increment();
// 处理自定义样式
const customStyle = reactive<IData>({
[ns.cssVarBlockName('z-index')]: drawerZIndex,
});
// 执行抽屉关闭操作
const doDrawerClose = () => {
setTimeout(() => {
zIndex.decrement();
proxy.$destroy();
document.body.removeChild(proxy.$el);
}, 300);
};
// 抽屉自身的操作触发的关闭(如点右上角的×)
const onVisibleChange = (state: boolean) => {
if (!state) {
props.resolve({ ok: false, data: {} });
doDrawerClose();
}
};
// modal对象
const modal: IModal = { mode: ViewMode.DRAWER };
// 抽屉标题
const drawerCaption = ref('');
// 视图神经元接收
const onNeuronInit = (neuron: ViewNeuron) => {
neuron.evt.on('setTitle', title => {
drawerCaption.value = title;
});
neuron.evt.on('closeView', (res?: IModalData) => {
props.resolve(res);
doDrawerClose();
});
};
return {
ns,
modal,
isShow,
customStyle,
drawerCaption,
onNeuronInit,
onVisibleChange,
};
},
render(h) {
return (
<drawer
value={this.isShow}
on-input={(val: boolean) => {
this.isShow = val;
}}
width={this.opts.width}
height={this.opts.height}
class={[this.ns.b()]}
style={this.customStyle}
placement={this.opts.placement || 'right'}
on-on-visible-change={this.onVisibleChange}
>
{h(this.componentName, {
props: {
...this.componentProps,
modal: this.modal,
},
on: {
neuronInit: this.onNeuronInit,
},
})}
</drawer>
);
},
});
import { IModalData } from '@ibiz-template/runtime';
import Vue from 'vue';
import { AppDrawerOptions } from '@/interface';
import { AppDrawerComponent } from './app-drawer-component';
import { piniaInstance } from '@/store';
const PlacementMap: IData = {
DRAWER_LEFT: 'left',
DRAWER_RIGHT: 'right',
DRAWER_TOP: 'top',
DRAWER_BOTTOM: 'bottom',
};
export class AppDrawer {
/**
* 获取打开方式对应的placement
*
* @author lxm
* @date 2022-09-15 17:09:46
* @static
* @param {string} openMode
* @returns {*} {AppDrawerOptions['placement']}
*/
static getPlacement(openMode: string): AppDrawerOptions['placement'] {
return PlacementMap[openMode] || 'right';
}
/**
* 打开抽屉
*
* @author lxm
* @date 2022-09-09 17:09:55
* @static
* @param {string} componentName 模态内部绘制组件名称
* @param {IData} props 模态内部绘制组件的props
* @param {AppDrawerOptions} [opts] 模态相关的配置参数
* @returns {*} {Promise<IModalData>}
*/
static openDrawer(
componentName: string,
props: IData,
opts?: AppDrawerOptions,
): Promise<IModalData> {
return new Promise((resolve, reject) => {
const vm = new Vue({
pinia: piniaInstance,
render(h) {
return h(AppDrawerComponent, {
props: {
componentName,
componentProps: props,
opts,
resolve,
reject,
},
});
},
}).$mount();
document.body.appendChild(vm.$el);
});
}
}
import {
defineComponent,
getCurrentInstance,
PropType,
reactive,
ref,
} from 'vue';
import { useNamespace } from '@ibiz-template/vue-util';
import '@/styles/components/util/modal/modal.scss';
import { ViewNeuron } from '@ibiz-template/controller';
import { isNumber } from 'lodash-es';
import { IModal, IModalData, ViewMode } from '@ibiz-template/runtime';
import { AppModalOptions } from '@/interface';
import { useUIStore } from '@/store';
export const AppModalComponent = defineComponent({
props: {
componentName: String,
componentProps: Object as PropType<IData>,
opts: {
type: Object as PropType<AppModalOptions>,
default: () => ({}),
},
resolve: { type: Function, required: true },
reject: { type: Function, required: true },
},
setup(props) {
const ns = useNamespace('modal');
const isShow = ref(true);
const { proxy } = getCurrentInstance()!;
const { zIndex } = useUIStore();
const modalZIndex = zIndex.increment();
// 处理自定义样式,添加z-index变量
const customStyle = reactive<IData>({});
const { width, height } = props.opts;
if (width) {
customStyle.width = isNumber(width) ? `${width}px` : width;
}
if (height) {
customStyle.height = isNumber(height) ? `${height}px` : height;
}
// 执行模态关闭操作
const doModalClose = () => {
setTimeout(() => {
zIndex.decrement();
proxy.$destroy();
document.body.removeChild(proxy.$el);
}, 300);
};
// 模态自身的操作触发的关闭(如点右上角的×)
const onVisibleChange = (state: boolean) => {
if (!state) {
props.resolve({ ok: false, data: {} });
doModalClose();
}
};
// 模态对象
const modal: IModal = { mode: ViewMode.MODAL };
const modalCaption = ref('');
const onNeuronInit = (neuron: ViewNeuron) => {
neuron.evt.on('setTitle', title => {
modalCaption.value = title;
});
neuron.evt.on('closeView', (res: IModalData) => {
props.resolve(res);
doModalClose();
});
};
return {
ns,
modal,
isShow,
modalZIndex,
customStyle,
modalCaption,
onNeuronInit,
onVisibleChange,
};
},
render(h) {
return (
<modal
value={this.isShow}
on-input={(val: boolean) => {
this.isShow = val;
}}
class={[this.ns.b()]}
style={{ [this.ns.cssVarBlockName('z-index')]: this.modalZIndex }}
styles={this.customStyle}
footer-hide={this.opts.footerHide}
on-on-visible-change={this.onVisibleChange}
>
{h(this.componentName, {
props: {
...this.componentProps,
modal: this.modal,
},
on: {
neuronInit: this.onNeuronInit,
},
})}
</modal>
);
},
});
import { IModalData } from '@ibiz-template/runtime';
import Vue from 'vue';
import { AppModalOptions } from '@/interface';
import { AppModalComponent } from './app-modal-component';
import { piniaInstance } from '@/store';
export class AppModal {
/**
* 打开模态
*
* @author lxm
* @date 2022-09-09 17:09:55
* @static
* @param {string} componentName 模态内部绘制组件名称
* @param {IData} props 模态内部绘制组件的props
* @param {AppModalOptions} [opts] 模态相关的配置参数
* @returns {*} {Promise<IModalData>}
*/
public static openModal(
componentName: string,
props: IData,
opts?: AppModalOptions,
): Promise<IModalData> {
return new Promise((resolve, reject) => {
const vm = new Vue({
pinia: piniaInstance,
render(h) {
return h(AppModalComponent, {
props: {
componentName,
componentProps: props,
opts,
resolve,
reject,
},
});
},
}).$mount();
document.body.appendChild(vm.$el);
});
}
}
import { IBizContext } from '@ibiz-template/core';
import { getViewInfo, IPSAppView } from '@ibiz-template/model';
import { IModalData, IOpenViewUtil } from '@ibiz-template/runtime';
import { generateRoutePath } from '@ibiz-template/vue-util';
import router from '@/router';
import { AppModal } from './app-modal/app-modal';
import { getViewComponentName } from '../component-name';
import { AppDrawer } from './app-drawer/app-drawer';
/**
* 打开视图方式工具类
*
* @description 此实现类挂载在 ibiz.openViewUtil
* @author chitanda
* @date 2022-08-16 20:08:54
* @export
* @class OpenViewUtil
* @implements {IOpenViewUtil}
*/
export class OpenViewUtil implements IOpenViewUtil {
root(
appView: IPSAppView,
context?: IBizContext | undefined,
_params?: IParams | undefined,
): void {
const path = generateRoutePath(
appView,
router.currentRoute,
context,
_params,
);
router.push({ path });
}
/**
* 模态打开视图
*
* @author lxm
* @date 2022-09-12 01:09:06
* @param {IPSAppView} appView
* @param {(IBizContext | undefined)} [context]
* @param {(IParams | undefined)} [params]
* @returns {*} {Promise<IModalData>}
*/
async modal(
appView: IPSAppView,
context?: IBizContext | undefined,
params?: IParams | undefined,
): Promise<IModalData> {
// 获取视图组件名和path
const { viewType, modelPath } = getViewInfo(appView);
const viewComponentName = getViewComponentName(viewType);
// 设置默认的modal参数
const opts = {
width: appView.width || '80%',
height: appView.height || '80%',
footerHide: true,
};
const res = await AppModal.openModal(
viewComponentName,
{
context,
params,
modelPath,
},
opts,
);
return res;
}
async popover(
appView: IPSAppView,
context?: IBizContext | undefined,
params?: IParams | undefined,
): Promise<IModalData> {
console.log('openPopover', appView, context, params);
throw new Error();
}
/**
* 抽屉打开视图
*
* @author lxm
* @date 2022-09-15 15:09:50
* @param {IPSAppView} appView
* @param {(IBizContext | undefined)} [context]
* @param {(IParams | undefined)} [params]
* @returns {*} {Promise<IModalData>}
*/
async drawer(
appView: IPSAppView,
context?: IBizContext | undefined,
params?: IParams | undefined,
): Promise<IModalData> {
// 获取视图组件名和path
const { viewType, modelPath } = getViewInfo(appView);
const viewComponentName = getViewComponentName(viewType);
const placement = AppDrawer.getPlacement(appView.openMode);
// 设置默认的modal参数
const opts = {
width: appView.width || '800',
height: appView.height || '600',
placement,
};
const res = await AppDrawer.openDrawer(
viewComponentName,
{
context,
params,
modelPath,
},
opts,
);
return res;
}
async custom(
appView: IPSAppView,
context?: IBizContext | undefined,
params?: IParams | undefined,
): Promise<IModalData> {
console.log('openUserCustom', appView, context, params);
throw new Error();
}
}
import { defineComponent } from 'vue';
export default defineComponent({
render() {
return <div>404</div>;
},
});
import { useRouter } from '@ibiz-template/vue-util';
import Vue, { ref, watch } from 'vue';
export const getView2Value = (item: IParams) => {
return (
(item.params.view2 ? `/${item.params.view2}` : '') +
(item.params.params2 ? `/${item.params.params2}` : '')
);
};
export const getView1Value = (item: IParams) => {
return (
(item.params.view1 ? `/${item.params.view1}` : '') +
(item.params.params1 ? `/${item.params.params1}` : '')
);
};
export interface RouteMsg {
key: string;
fullPath: string;
modelPath?: string;
caption?: string;
}
export function useIndexRouteManage(proxy: Vue) {
const router = useRouter(proxy);
/** 当前的路由标识,只由二级路由组成,二级路由一致则就是同一个视图 */
const currentKey = ref('');
/** 留一份首页的Path */
const indexPath = ref('');
/** key操作记录,只维护当前缓存的key,且每个key在集合里唯一,最新操作的排在最前面 */
const keyHistory = ref<string[]>([]);
/** 路由信息,每个key对应的全路由和标题之类的信息 */
const routeMsgs = ref<RouteMsg[]>([]);
// 监听路由
watch(
() => proxy.$route,
(newVal, oldVal) => {
// 只处理有二级路由,只有首页的时候不需要
if (newVal !== oldVal && newVal.params.view2) {
currentKey.value = getView2Value(newVal);
indexPath.value = getView1Value(newVal);
// 更新或新建对应key的全路由信息,主要是三级路由变更时会用
const find = routeMsgs.value.find(
item => item.key === currentKey.value,
);
if (find) {
find.fullPath = newVal.fullPath;
} else {
routeMsgs.value.push({
key: currentKey.value,
fullPath: newVal.fullPath,
modelPath: '',
caption: '',
});
}
}
},
{ deep: true, immediate: true },
);
// 监听当前的key,维护数据
watch(
currentKey,
(newVal, oldVal) => {
if (newVal !== oldVal && newVal) {
const index = keyHistory.value.indexOf(newVal);
// 历史记录里没有的新建信息,放入开头
if (index === -1) {
keyHistory.value.unshift(newVal);
} else {
// 已存在的调整顺序至开头
keyHistory.value.splice(index, 1);
keyHistory.value.unshift(newVal);
}
}
},
{ immediate: true },
);
/**
* 更新路由信息
*
* @author lxm
* @date 2022-09-01 16:09:51
* @param {string} key
* @param {IData} opts
*/
const updateRouteMsg = (key: string, opts: Partial<RouteMsg>) => {
const find = routeMsgs.value.find(item => item.key === currentKey.value);
if (find) {
if (opts.caption) find.caption = opts.caption;
if (opts.modelPath) find.modelPath = opts.modelPath;
}
};
/**
* 删除路由缓存数据
*
* @author lxm
* @date 2022-09-22 10:09:11
* @param {string[]} keys 要删除的keys
*/
const deleteRouteCache = (keys: string[]) => {
keys.forEach(key => {
const index = keyHistory.value.indexOf(key);
if (index !== -1) {
keyHistory.value.splice(index, 1);
const msgIndex = routeMsgs.value.findIndex(item => item.key === key);
routeMsgs.value.splice(msgIndex, 1);
}
});
};
/**
* 关闭视图回调
*
* @author lxm
* @date 2022-09-22 10:09:59
* @param {string} [key=currentKey.value]
*/
const closeView = (key: string = currentKey.value) => {
// 找出删除的key在历史记录里的位置
deleteRouteCache([key]);
const toKey = keyHistory.value[0];
if (!toKey) {
currentKey.value = '';
router.push(indexPath.value);
} else {
const find = routeMsgs.value.find(item => item.key === toKey);
router.push(find!.fullPath);
}
};
return {
currentKey,
keyHistory,
routeMsgs,
updateRouteMsg,
closeView,
deleteRouteCache,
};
}
<script setup lang="ts">
import { ViewNeuron } from '@ibiz-template/controller';
import { useIndexViewController } from '@ibiz-template/vue-util';
import { computed, getCurrentInstance, onMounted } from 'vue';
import { useIndexRouteManage } from './index-view';
interface IndexViewProps {
context: IContext;
params?: IParams;
modelPath: string;
}
const props = withDefaults(defineProps<IndexViewProps>(), {
params: () => ({}),
});
const { proxy } = getCurrentInstance()!;
const c = useIndexViewController(proxy, props.modelPath);
const {
currentKey,
keyHistory,
routeMsgs,
updateRouteMsg,
closeView,
deleteRouteCache,
} = useIndexRouteManage(proxy);
// 视图初始化,监听事件
const onCreated = (neuron: ViewNeuron) => {
const key = currentKey.value;
neuron.evt.on('closeView', () => {
closeView(key);
});
neuron.evt.on('setTitle', (title: string) => {
updateRouteMsg(key, { caption: title });
});
};
onMounted(() => {
setTimeout(() => {
const el = document.querySelector('.app-loading-x') as HTMLDivElement;
if (el) {
el.style.display = 'none';
}
}, 300);
});
const onViewFound = (opts: IData) => {
updateRouteMsg(currentKey.value, opts);
};
// 菜单收缩变化
const collapseChange = (collapse: boolean) => {
c.collapseChange = collapse;
};
const onMenuRouteChange = () => {
deleteRouteCache(keyHistory.value.slice(1));
};
const onBackClick = () => {
closeView();
console.log('onBackClick');
};
const currentPath = computed(() => {
const routeMsg = routeMsgs.value.find(item => item.key === currentKey.value);
return routeMsg?.modelPath || '';
});
</script>
<template>
<AppLayout
:is-complete="c.complete"
:model="c.model"
@onCollapseChange="collapseChange"
@backClick="onBackClick"
>
<template v-if="c.complete">
<AppMenu
v-if="c.complete"
slot="menu"
:current-path="currentPath"
:model-data="c.model.appMenu"
:context="c.context"
:collapse-change="c.collapseChange"
@menuRouteChange="onMenuRouteChange"
></AppMenu>
<AppKeepAlive :key-list="keyHistory">
<router-view
:key="currentKey"
@neuronInit="onCreated"
@viewFound="onViewFound"
/>
</AppKeepAlive>
</template>
</AppLayout>
</template>
import IndexView from './index-view/index-view.vue';
export { IndexView };
import { CoreConst } from '@ibiz-template/core';
import {
defineComponent,
getCurrentInstance,
onMounted,
reactive,
ref,
} from 'vue';
import { setCookie } from 'qx-util';
import { useNamespace, useRoute } from '@ibiz-template/vue-util';
import router from '@/router';
import '@/styles/components/views/login-view/login-view.scss';
interface LoginData {
username: string;
password: string;
}
const rules = {
username: [
{
required: true,
message: '请输入账号',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur',
},
{
type: 'string',
min: 6,
message: '密码长度不能少于6位',
trigger: 'blur',
},
],
};
export default defineComponent({
setup() {
const ns = useNamespace('login-view');
const loginData = reactive<LoginData>({
username: 'zjbjadmin',
password: '123456',
});
const formRef = ref<IData | null>(null);
const instance = getCurrentInstance()!;
const route = useRoute(instance.proxy);
const ru = (route.query.ru as string) || '/';
onMounted(() => {
setTimeout(() => {
const el = document.querySelector('.app-loading-x') as HTMLDivElement;
if (el) {
el.style.display = 'none';
}
}, 300);
});
const onClick = () => {
formRef.value!.validate(async (valid: boolean) => {
if (valid) {
try {
const res = await ibiz.auth.v7login(
loginData.username,
loginData.password,
);
if (res.ok) {
const { data } = res;
if (data && data.token) {
setCookie(CoreConst.TOKEN, data.token, 7, true);
router.push({ path: ru });
return;
}
}
ibiz.notification.error({
title: res.data?.message || '登录失败',
});
} catch (error) {
ibiz.notification.error({
title: (error as IData).response?.data?.message || '登录失败',
});
}
}
});
};
return () => (
<div class={ns.b()}>
<div class={ns.b('box')}>
<header class={ns.b('box-header')}>
<img src='/img/login-header.png' />
</header>
<main class={ns.b('box-main')}>
<img
class={ns.be('box-main', 'avatar')}
src='/img/login-avatar.png'
/>
<div class={ns.b('box-main-content')}>
<i-form ref={formRef} props={{ model: loginData, rules }}>
<form-item prop='username'>
<i-input
type='text'
value={loginData.username}
on-on-change={(evt: InputEvent) => {
const { value } = evt.target as HTMLInputElement;
loginData.username = value;
}}
placeholder='请输入账号'
size='large'
>
<icon type='md-person' slot='prefix'></icon>
</i-input>
</form-item>
<form-item prop='password'>
<i-input
type='password'
value={loginData.password}
on-on-change={(evt: InputEvent) => {
const { value } = evt.target as HTMLInputElement;
loginData.password = value;
}}
placeholder='请输入密码'
size='large'
password
>
<icon type='ios-unlock' slot='prefix'></icon>
</i-input>
</form-item>
<form-item>
<i-button shape='circle' long on-click={onClick}>
登录
</i-button>
</form-item>
</i-form>
</div>
</main>
</div>
</div>
);
},
});
{
"vueCompilerOptions": {
"target": 2.7
},
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"allowJs": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts"
]
}
\ No newline at end of file
import { defineConfig } from 'vite';
import path from 'path';
import vue from '@vitejs/plugin-vue2';
import vueJsx from '@vitejs/plugin-vue2-jsx';
import eslint from 'vite-plugin-eslint';
// import legacy from '@vitejs/plugin-legacy'; // ie11 开启此配置
import { visualizer } from 'rollup-plugin-visualizer'; // 打包内容分析
// https://vitejs.dev/config/
export default defineConfig({
base: './',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
server: {
proxy: {
'/pms__sclpmswebapp': {
target: 'http://172.16.103.120:30060',
changeOrigin: true,
},
'/trainsys__web': {
target: 'http://172.16.240.140:20086',
changeOrigin: true,
},
},
cors: true,
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/global.scss";',
},
},
},
plugins: [
eslint({
include: 'src/**/*.{ts,tsx,js,jsx}',
}),
vue(),
vueJsx(),
visualizer(),
// legacy({ targets: ['ie >= 11'] }),
],
});
......@@ -194,7 +194,7 @@
</changeSet>
<!--输出实体[REGINFO]数据结构 -->
<changeSet author="root" id="tab-reginfo-97-8">
<changeSet author="root" id="tab-reginfo-99-8">
<createTable tableName="T_REGINFO">
<column name="UPDATEDATE" remarks="" type="DATETIME">
</column>
......@@ -303,7 +303,7 @@
</changeSet>
<!--输出实体[REGINFO]外键关系 -->
<changeSet author="root" id="fk-reginfo-97-12">
<changeSet author="root" id="fk-reginfo-99-12">
<addForeignKeyConstraint baseColumnNames="STUDENTID" baseTableName="T_REGINFO" constraintName="F328B6E61632896255" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="STUDENTID" referencedTableName="T_STUDENT" validate="true"/>
</changeSet>
......
......@@ -1280,7 +1280,7 @@
},
"dynaModelFilePath" : "PSSYSAPPS/Web/PSSYSAPP.json",
"name" : "网页端",
"pFStyle" : "8A9FA120-F688-44AF-B71D-3C8B745A6F0B",
"pFStyle" : "full_dynamic_vue",
"pFType" : "VUE_R7",
"pKGCodeName" : "Web",
"serviceCodeName" : "Web",
......
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册