Browse Source

组件重写

main
likunming 1 year ago
parent
commit
cd980c3b86
  1. 166
      io.sc.platform.components.frontend/src/platform/components/PlatformGrid.vue
  2. 1
      io.sc.platform.components.frontend/src/platform/index.ts
  3. 1
      io.sc.platform.components.frontend/src/platform/utils/index.ts
  4. 1
      io.sc.platform.components.frontend/src/views/View1.vue
  5. 93
      io.sc.platform.components.frontend/src/views/View2.vue
  6. 18
      io.sc.platform.core.frontend/src/components/index.ts
  7. 73
      io.sc.platform.core.frontend/src/menus/menus.json
  8. 93
      io.sc.platform.core.frontend/src/platform/components/drawer/WDrawer.vue
  9. 197
      io.sc.platform.core.frontend/src/platform/components/form/WForm.vue
  10. 39
      io.sc.platform.core.frontend/src/platform/components/form/elements/WCheckbox.vue
  11. 94
      io.sc.platform.core.frontend/src/platform/components/form/elements/WDate.vue
  12. 78
      io.sc.platform.core.frontend/src/platform/components/form/elements/WNumber.vue
  13. 76
      io.sc.platform.core.frontend/src/platform/components/form/elements/WSelect.vue
  14. 75
      io.sc.platform.core.frontend/src/platform/components/form/elements/WText.vue
  15. 87
      io.sc.platform.core.frontend/src/platform/components/form/elements/WTextBtn.vue
  16. 75
      io.sc.platform.core.frontend/src/platform/components/form/elements/WTextarea.vue
  17. 15
      io.sc.platform.core.frontend/src/platform/components/grid/EnableIcon.vue
  18. 2311
      io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue
  19. 46
      io.sc.platform.core.frontend/src/platform/components/index.ts
  20. 95
      io.sc.platform.core.frontend/src/platform/components/panel/WInfoPanel.vue
  21. 57
      io.sc.platform.core.frontend/src/platform/components/toolbar/ChildrenBtn.vue
  22. 284
      io.sc.platform.core.frontend/src/platform/components/toolbar/WToolbar.vue
  23. 58
      io.sc.platform.core.frontend/src/platform/components/utils/FormValidators.ts
  24. 8
      io.sc.platform.core.frontend/src/platform/components/utils/componentComm.ts
  25. 2
      io.sc.platform.core.frontend/src/platform/components/utils/index.ts
  26. 2
      io.sc.platform.core.frontend/src/platform/index.ts
  27. 94
      io.sc.platform.core.frontend/src/routes/routes.json
  28. 30
      io.sc.platform.core.frontend/src/views/Form1.vue
  29. 4
      io.sc.platform.core.frontend/src/views/Grid1.vue
  30. 13
      io.sc.platform.core.frontend/src/views/likm/A.vue
  31. 35
      io.sc.platform.core.frontend/src/views/likm/Dialog.vue
  32. 34
      io.sc.platform.core.frontend/src/views/likm/Drawer.vue
  33. 152
      io.sc.platform.core.frontend/src/views/likm/Form.vue
  34. 220
      io.sc.platform.core.frontend/src/views/likm/Grid.vue
  35. 308
      io.sc.platform.core.frontend/src/views/likm/GridLayout.vue
  36. 15
      io.sc.platform.core.frontend/src/views/likm/InfoPanel.vue
  37. 154
      io.sc.platform.core.frontend/src/views/likm/QuasarGrid.vue
  38. 153
      io.sc.platform.core.frontend/src/views/likm/Toolbar.vue
  39. 30
      io.sc.platform.core.frontend/template-project/src/views/Form1.vue
  40. 4
      io.sc.platform.core.frontend/template-project/src/views/Grid1.vue

166
io.sc.platform.components.frontend/src/platform/components/PlatformGrid.vue

@ -243,17 +243,22 @@
</template> </template>
</template> </template>
<template v-if="tableRowDrag" #body-cell="scope"> <template v-if="tableRowDrag" #body-cell="scope">
<q-td :props="scope"> <q-td
<PlatformGridTdDrag :props="scope"
v-if="scope.col.field === '_sortNo_'" draggable="true"
:td-value="[scope.rowIndex + 1]" @dragenter="onDragEnter($event, scope)"
:row-index="scope.rowIndex" @dragleave="onDragLeave"
@table-sort-fun="tableSortFun" @dragover="onDragOver($event, scope)"
></PlatformGridTdDrag> @drop="onDrop($event, scope)"
<PlatformGridTdDrag v-else :td-value="[scope.value]" :row-index="scope.rowIndex" @table-sort-fun="tableSortFun"></PlatformGridTdDrag> @dragstart="onDragStart($event, scope)"
<q-tooltip v-if="scope.col.classes?.indexOf('truncate') > -1 && scope.value && typeof scope.value === 'string'"> >
<template v-if="scope.col.field === '_sortNo_'">{{ scope.rowIndex + 1 }}</template>
<template v-else>
{{ scope.value }} {{ scope.value }}
</q-tooltip> <q-tooltip v-if="scope.col.classes?.indexOf('truncate') > -1">
{{ scope.value }}
</q-tooltip>
</template>
</q-td> </q-td>
</template> </template>
<template v-else #body-cell="scope"> <template v-else #body-cell="scope">
@ -349,13 +354,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick, toRaw } from 'vue'; import { ref, reactive, computed, onMounted, nextTick, toRaw } from 'vue';
import { axios, Environment, i18n } from 'platform-core'; import { axios, Environment } from 'platform-core';
import { useQuasar, getCssVar } from 'quasar'; import { useQuasar, getCssVar } from 'quasar';
import PlatformDialog from './PlatformDialog.vue'; import PlatformDialog from './PlatformDialog.vue';
import PlatformDrawer from './PlatformDrawer.vue'; import PlatformDrawer from './PlatformDrawer.vue';
import PlatformInfo from './PlatformInfo.vue'; import PlatformInfo from './PlatformInfo.vue';
import PlatformForm from './PlatformForm.vue'; import PlatformForm from './PlatformForm.vue';
import PlatformGridTdDrag from './PlatformGridTdDrag.vue';
import { import {
extractTableProps, extractTableProps,
extractTableColumnsProps, extractTableColumnsProps,
@ -367,7 +371,6 @@ import {
isEmpty, isEmpty,
PageStatusEnum, PageStatusEnum,
PlatformNotifyTypeEnum, PlatformNotifyTypeEnum,
CriteriaType,
} from '@/platform/utils'; } from '@/platform/utils';
import '@/css/tailwind.css'; import '@/css/tailwind.css';
@ -921,6 +924,12 @@ const onResize = () => {
} }
}; };
type CriteriaType = {
fieldName: string;
operator: OperatorTypeEnum;
value: any;
};
// criteria // criteria
const buildQueryCriterias = (reqParams) => { const buildQueryCriterias = (reqParams) => {
const urlSearchParams = new URLSearchParams(reqParams); const urlSearchParams = new URLSearchParams(reqParams);
@ -1344,34 +1353,6 @@ const stickyHeaderColumn = () => {
} }
}; };
onMounted(() => {
if (extractTableColumns && extractTableColumns.length > props.tableColumns.length) {
handlerMoreRowColumnTitle();
}
if (props.tableInitLoadData) {
onRequest({
pagination: table.pagination,
});
} else {
stickyHeaderColumn();
}
});
defineExpose({
onView,
onDelete,
getSelectedRows,
refreshTable,
replaceRowsFun,
replaceRowFun,
rowClickFun,
getRowsFun,
deleteRowsFun,
addRowFun,
getQueryFormRefFun,
getAddEditFormRefFun,
});
const excludeColumnNum = computed(() => { const excludeColumnNum = computed(() => {
let num = 0; let num = 0;
if (props.tableSelection !== 'none') { if (props.tableSelection !== 'none') {
@ -1578,9 +1559,110 @@ const tableTitleClassComputed = computed(() => {
} }
return classArr; return classArr;
}); });
//
let dragRowIndex;
const onDragStart = (e, scope) => {
dragRowIndex = scope.rowIndex;
e.dataTransfer.dropEffect = 'move';
};
const addDragTopStyle = (e) => {
e.target.parentNode.style.borderTopWidth = '2px';
e.target.parentNode.style.borderTopStyle = 'dashed';
e.target.parentNode.style.borderTopColor = 'orange';
};
const removeDragTopStyle = (e) => {
e.target.parentNode.style.borderTopWidth = '';
e.target.parentNode.style.borderTopStyle = '';
e.target.parentNode.style.borderTopColor = '';
};
const addDragBottomStyle = (e) => {
e.target.parentNode.style.borderBottomWidth = '2px';
e.target.parentNode.style.borderBottomStyle = 'dashed';
e.target.parentNode.style.borderBottomColor = 'orange';
};
const removeDragBottomStyle = (e) => {
e.target.parentNode.style.borderBottomWidth = '';
e.target.parentNode.style.borderBottomStyle = '';
e.target.parentNode.style.borderBottomColor = '';
};
//
const onDragEnter = (e, scope) => {
if (dragRowIndex !== scope.rowIndex && e.target.parentNode.parentNode.children.length === scope.rowIndex + 1) {
//
addDragBottomStyle(e);
} else if (dragRowIndex !== scope.rowIndex) {
addDragTopStyle(e);
}
};
//
const onDragLeave = (e) => {
removeDragTopStyle(e);
removeDragBottomStyle(e);
};
//
const onDragOver = (e, scope) => {
if (e.offsetY >= 24 && dragRowIndex !== scope.rowIndex && e.target.parentNode.parentNode.children.length === scope.rowIndex + 1) {
removeDragTopStyle(e);
addDragBottomStyle(e);
} else if (e.offsetY < 24 && dragRowIndex !== scope.rowIndex && e.target.parentNode.parentNode.children.length === scope.rowIndex + 1) {
removeDragBottomStyle(e);
addDragTopStyle(e);
}
e.preventDefault();
};
//
const onDrop = (e, scope) => {
e.preventDefault();
if (dragRowIndex === scope.rowIndex) {
return;
}
const dragRow = table.rows[dragRowIndex];
if (e.offsetY < 24 && dragRowIndex !== scope.rowIndex && e.target.parentNode.parentNode.children.length === scope.rowIndex + 1) {
table.rows.splice(dragRowIndex, 1);
table.rows.splice(scope.rowIndex - 1, 0, dragRow);
} else {
table.rows.splice(dragRowIndex, 1);
table.rows.splice(scope.rowIndex, 0, dragRow);
}
removeDragTopStyle(e);
removeDragBottomStyle(e);
};
onMounted(() => {
if (extractTableColumns && extractTableColumns.length > props.tableColumns.length) {
handlerMoreRowColumnTitle();
}
if (props.tableInitLoadData) {
onRequest({
pagination: table.pagination,
});
} else {
stickyHeaderColumn();
}
});
defineExpose({
onView,
onDelete,
getSelectedRows,
refreshTable,
replaceRowsFun,
replaceRowFun,
rowClickFun,
getRowsFun,
deleteRowsFun,
addRowFun,
getQueryFormRefFun,
getAddEditFormRefFun,
});
</script> </script>
<style scope lang="css"> <style scoped lang="css">
:deep(.q-table) {
border-collapse: collapse;
}
/** 表格最后一行数据底部边框没有,通过强制覆盖组件样式增加底部边框 */ /** 表格最后一行数据底部边框没有,通过强制覆盖组件样式增加底部边框 */
.q-table--vertical-separator td, .q-table--vertical-separator td,
.q-table--vertical-separator th, .q-table--vertical-separator th,

1
io.sc.platform.components.frontend/src/platform/index.ts

@ -17,7 +17,6 @@ export { DataTypeEnum } from './utils';
export { OptionComeFromEnum } from './utils'; export { OptionComeFromEnum } from './utils';
export { FormComponentValidateEnum } from './utils'; export { FormComponentValidateEnum } from './utils';
export { OperatorTypeEnum } from './utils'; export { OperatorTypeEnum } from './utils';
export { CriteriaType } from './utils';
export { PlatformNotifyTypeEnum } from './utils'; export { PlatformNotifyTypeEnum } from './utils';
export { platformNotify } from './utils'; export { platformNotify } from './utils';
export { isEmpty } from './utils'; export { isEmpty } from './utils';

1
io.sc.platform.components.frontend/src/platform/utils/index.ts

@ -5,7 +5,6 @@ export { DataTypeEnum } from './commUtil';
export { OptionComeFromEnum } from './commUtil'; export { OptionComeFromEnum } from './commUtil';
export { FormComponentValidateEnum } from './commUtil'; export { FormComponentValidateEnum } from './commUtil';
export { OperatorTypeEnum } from './commUtil'; export { OperatorTypeEnum } from './commUtil';
export { CriteriaType } from './commUtil';
export { PlatformNotifyTypeEnum } from './commUtil'; export { PlatformNotifyTypeEnum } from './commUtil';
export { platformNotify } from './commUtil'; export { platformNotify } from './commUtil';
export { isEmpty } from './commUtil'; export { isEmpty } from './commUtil';

1
io.sc.platform.components.frontend/src/views/View1.vue

@ -8,6 +8,7 @@
:table-row-key="dataSupplementTypeGrid.tableRowKey" :table-row-key="dataSupplementTypeGrid.tableRowKey"
:table-init-load-data="dataSupplementTypeGrid.tableInitLoadData" :table-init-load-data="dataSupplementTypeGrid.tableInitLoadData"
:table-data-url="dataSupplementTypeGrid.tableDataUrl" :table-data-url="dataSupplementTypeGrid.tableDataUrl"
:table-row-drag="false"
:table-show-sort-no="dataSupplementTypeGrid.tableShowSortNo" :table-show-sort-no="dataSupplementTypeGrid.tableShowSortNo"
:table-columns="dataSupplementTypeGrid.tableColumns" :table-columns="dataSupplementTypeGrid.tableColumns"
:table-left-column-sticky-number="dataSupplementTypeGrid.tableLeftColumnStickyNumber" :table-left-column-sticky-number="dataSupplementTypeGrid.tableLeftColumnStickyNumber"

93
io.sc.platform.components.frontend/src/views/View2.vue

@ -1,9 +1,94 @@
<template> <template>
<div>{{ message }}</div> <div>
<PlatformGrid
ref="dataSupplementTypeGridRef"
:query-form-cols-number="dataSupplementTypeGrid.queryFormColsNumber"
:query-form-cols-auto="dataSupplementTypeGrid.queryFormColsAuto"
:table-title="dataSupplementTypeGrid.tableTitle"
:table-row-key="dataSupplementTypeGrid.tableRowKey"
:table-init-load-data="dataSupplementTypeGrid.tableInitLoadData"
:table-data-url="dataSupplementTypeGrid.tableDataUrl"
:table-row-drag="true"
:table-show-sort-no="dataSupplementTypeGrid.tableShowSortNo"
:table-columns="dataSupplementTypeGrid.tableColumns"
:table-left-column-sticky-number="dataSupplementTypeGrid.tableLeftColumnStickyNumber"
:table-buttons="dataSupplementTypeGrid.tableButtons"
:query-form-fields="dataSupplementTypeGrid.queryFormFields"
:table-pagination="dataSupplementTypeGrid.tablePagination"
:add-form-props="dataSupplementTypeGrid.addFormProps"
></PlatformGrid>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Environment, axios } from 'platform-core'; import { ref } from 'vue';
import { Environment } from 'platform-core';
import { PlatformGrid } from '@/platform';
import { PlatformIconEnum } from '@/platform/utils';
const dataSupplementTypeGridRef = ref();
const dataSupplementContentDialogRef = ref();
const response = await axios.post(Environment.apiContextPath('/api/sample/action2')); const dataSupplementTypeGrid = {
const message = response.data.message; queryFormColsNumber: 2,
queryFormColsAuto: false,
queryFormFields: [
{ label: '数据补录类型名称', modelName: 'typeName', type: 'text' },
{ label: '数据补录类型说明', modelName: 'typeDesc', type: 'text' },
],
hideBottom: false,
tableInitLoadData: true,
tableLeftColumnStickyNumber: 0,
tableTitle: '数据补录类型列表',
tableRowKey: 'id',
tableDataUrl: Environment.apiContextPath('api/data/supplement/type'),
tablePagination: {
sortBy: 'lastModifyDate',
descending: true,
reqPageStart: 0,
rowsPerPage: 10,
},
tableButtons: [
'query',
'reset',
'separator',
'add',
'edit',
'delete',
'separator',
{
icon: PlatformIconEnum.扳手,
label: '补录内容管理',
enableIf: (tableSelected) => {
if (tableSelected && tableSelected.length > 0) {
return true;
} else {
return false;
}
},
click: (tableSelected) => {
dataSupplementContentDialogRef.value.dialogShow(tableSelected[0]);
},
},
'separator',
'inFullscreen',
],
tableShowSortNo: true,
tableColumns: [
{ name: 'typeName', label: '数据补录类型名称' },
{ name: 'typeDesc', label: '数据补录类型说明' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
addFormProps: {
dialogInitWidth: '60%',
dialogInitHeight: '50%',
formColsNumber: 1,
formColsAuto: false,
formFields: [
{ label: '数据补录类型名称', modelName: 'typeName', type: 'text', required: true },
{ label: '数据补录类型说明', modelName: 'typeDesc', type: 'textarea' },
],
},
};
</script> </script>

18
io.sc.platform.core.frontend/src/components/index.ts

@ -6,14 +6,28 @@ import dialog from '@/views/Dialog.vue';
import codemirror from '@/views/Codemirror.vue'; import codemirror from '@/views/Codemirror.vue';
import select from '@/views/Select.vue'; import select from '@/views/Select.vue';
import treeGrid from '@/views/TreeGrid.vue'; import treeGrid from '@/views/TreeGrid.vue';
import table from '@/views/Table.vue'; import likmDialog from '@/views/likm/Dialog.vue';
import likmDrawer from '@/views/likm/Drawer.vue';
import likmForm from '@/views/likm/Form.vue';
import infoPanel from '@/views/likm/InfoPanel.vue';
import grid from '@/views/likm/Grid.vue';
import toolbar from '@/views/likm/Toolbar.vue';
import quasarGrid from '@/views/likm/QuasarGrid.vue';
import gridLayout from '@/views/likm/GridLayout.vue';
const localComponents = { const localComponents = {
'component.testcase.dialog': dialog, 'component.testcase.dialog': dialog,
'component.testcase.codemirror': codemirror, 'component.testcase.codemirror': codemirror,
'component.testcase.select': select, 'component.testcase.select': select,
'component.testcase.treeGrid': treeGrid, 'component.testcase.treeGrid': treeGrid,
'component.testcase.table': table, 'component.testcase.likmDialog': likmDialog,
'component.testcase.likmDrawer': likmDrawer,
'component.testcase.likmForm': likmForm,
'component.testcase.infoPanel': infoPanel,
'component.testcase.grid': grid,
'component.testcase.toolbar': toolbar,
'component.testcase.quasarGrid': quasarGrid,
'component.testcase.gridLayout': gridLayout,
}; };
export default localComponents; export default localComponents;

73
io.sc.platform.core.frontend/src/menus/menus.json

@ -62,5 +62,78 @@
"titleI18nKey": "menu.testcase.table", "titleI18nKey": "menu.testcase.table",
"icon": "bi-palette", "icon": "bi-palette",
"routeName": "route.testcase.table" "routeName": "route.testcase.table"
},
{ "type": "GROUP", "order": 30000, "id": "menu.testcase.likm", "titleI18nKey": "测试用例-likm", "icon": "home" },
{
"type": "ROUTE",
"order": 100,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.dialog",
"titleI18nKey": "Dialog测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.dialog"
},
{
"type": "ROUTE",
"order": 200,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.drawer",
"titleI18nKey": "Drawer测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.drawer"
},
{
"type": "ROUTE",
"order": 300,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.form",
"titleI18nKey": "Form测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.form"
},
{
"type": "ROUTE",
"order": 400,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.infoPanel",
"titleI18nKey": "InfoPanel测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.infoPanel"
},
{
"type": "ROUTE",
"order": 500,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.grid",
"titleI18nKey": "Grid测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.grid"
},
{
"type": "ROUTE",
"order": 600,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.toolbar",
"titleI18nKey": "Toolbar测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.toolbar"
},
{
"type": "ROUTE",
"order": 700,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.quasarGrid",
"titleI18nKey": "官网表格测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.quasarGrid"
},
{
"type": "ROUTE",
"order": 700,
"parentId": "menu.testcase.likm",
"id": "menu.testcase.likm.gridLayout",
"titleI18nKey": "grid布局测试",
"icon": "bi-palette",
"routeName": "route.testcase.likm.gridLayout"
} }
] ]

93
io.sc.platform.core.frontend/src/platform/components/drawer/WDrawer.vue

@ -0,0 +1,93 @@
<template>
<div>
<q-dialog v-model="drawer.show" position="right" v-bind="attrs" :maximized="true">
<q-card :style="drawerStyleComputed">
<div class="w-full h-full">
<div style="height: 69px">
<q-card-section>
<div class="flex justify-between">
<div class="text-h6">{{ title }}</div>
<div class="flex justify-end gap-4">
<template v-if="buttons && buttons.length > 0">
<template v-for="(btn, index) in buttons as any" :key="index">
<q-btn v-if="typeof btn === 'object'" :loading="false" :color="'primary'" v-bind="btn" @click="btn.click ? btn.click() : () => {}">
</q-btn>
</template>
</template>
<slot name="buttons"></slot>
<q-btn
v-if="canMaximized"
dense
flat
:icon="!drawer.actualMaximizedToggle ? IconEnum.全屏 : IconEnum.退出全屏"
@click="drawerMaximizeBtnClick"
>
<q-tooltip v-if="!drawer.actualMaximizedToggle">全屏</q-tooltip>
<q-tooltip v-else-if="drawer.actualMaximizedToggle">退出全屏</q-tooltip>
</q-btn>
<q-btn v-close-popup dense flat :icon="IconEnum.关闭">
<q-tooltip>关闭</q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<q-separator />
</div>
<div style="height: calc(100% - 69px)">
<q-card-section style="height: 100%; padding: 0px" class="scroll">
<slot></slot>
</q-card-section>
</div>
</div>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive, computed, useAttrs } from 'vue';
import { IconEnum } from '@/platform/enums';
const attrs = useAttrs();
const props = defineProps({
title: { type: String, default: '' },
width: { type: String, default: '50vw' },
height: { type: String, default: '100vh' },
canMaximized: { type: Boolean, default: true },
buttons: { type: Array, default: () => [] },
});
const drawer = reactive({
show: false,
/**
* 实际全屏属性Drawer 使用的 dialog 进行封装 dialog 的非全屏模式,
* 不管高宽给到多少Quasar 内置的 css 样式都会有一个 padding: 24px 的属性,
* 所以封装 Drawer 时默认就是 dialog 的全屏模式同时希望提供全屏的功能所以需要一个实际是否全屏的属性
*/
actualMaximizedToggle: false,
});
const drawerMaximizeBtnClick = () => {
drawer.actualMaximizedToggle = !drawer.actualMaximizedToggle;
};
const drawerStyleComputed = computed(() => {
if (!drawer.actualMaximizedToggle) {
return { width: props.width, 'max-width': '100vw', height: props.height, 'max-height': '100vh' };
} else {
return { width: '100vw', 'max-width': '100vw', height: '100vh', 'max-height': '100vh' };
}
});
const show = () => {
drawer.show = true;
};
const hide = () => {
drawer.show = false;
};
defineExpose({
show,
hide,
});
</script>

197
io.sc.platform.core.frontend/src/platform/components/form/WForm.vue

@ -0,0 +1,197 @@
<template>
<div>
<q-form ref="formRef" :autofocus="false" :greedy="true" v-bind="attrs">
<div class="grid" :class="formLayoutComputed">
<template v-for="(field, index) in fields as any" :key="String(index)">
<div
:class="
(field.colsFirst ? 'col-start-1 ' : ' ') +
(field.colspan === 'full'
? ' col-span-' + screenColsNumComputed
: field.colspan && screenColsNumComputed >= field.colspan
? ' col-span-' + field.colspan
: ' col-span-1')
"
>
<component :is="field.type" v-if="field.name" v-model="formData[field.name]" v-bind="field" :form-data="formData"></component>
<component :is="field.type" v-else :form-ref="formRef" v-bind="field" :form-data="formData"></component>
</div>
</template>
</div>
<slot></slot>
</q-form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed, toRaw, defineProps, useAttrs } from 'vue';
import { useQuasar } from 'quasar';
import { PageStatusEnum } from '@/platform/components/utils';
const $q = useQuasar();
const attrs = useAttrs();
const props = defineProps({
colsNum: { type: Number, default: 0 },
// colsNum 0
screenCols: {
type: Object,
default: () => {
return { xs: 1, sm: 2, md: 3, lg: 4, xl: 6 };
},
},
colsXGap: { type: Number, default: 8 },
colsYGap: { type: Number, default: 4 },
fields: {
type: Array,
default: () => {
return [];
},
},
});
const formRef = ref();
const formStatus = ref(PageStatusEnum.新增);
const formModel: any = {};
const formFieldsMap = new Map();
const defaultValueHandler = (field) => {
if (field.hasOwnProperty('defaultValue') && field.defaultValue !== null && field.defaultValue !== undefined) {
return field.defaultValue;
} else if (field.type === 'w-checkbox') {
return false;
}
return null;
};
watch(
() => props.fields,
(newVal, oldVal) => {
if (newVal) {
for (const field of props.fields as any) {
if (field.name) {
formModel[field.name] = defaultValueHandler(field);
formFieldsMap.set(field.name, field);
}
}
}
},
);
for (const field of props.fields as any) {
if (field.name) {
formModel[field.name] = defaultValueHandler(field);
formFieldsMap.set(field.name, field);
}
}
const formData = reactive(formModel);
const screenColsNumComputed = computed(() => {
if (props.colsNum > 0) {
return props.colsNum;
} else {
return props.screenCols[$q.screen.name];
}
});
const formLayoutComputed = computed(() => {
let className = '';
if (props.colsNum > 0) {
className = 'grid-cols-' + props.colsNum;
} else {
className = 'grid-cols-' + screenColsNumComputed.value;
}
className += ' gap-x-[' + props.colsXGap + 'px]';
className += ' gap-y-[' + props.colsYGap + 'px]';
return className;
});
/**
* 对外暴露方法-获取form所有数据
*/
const getData = () => {
const data = { ...toRaw(formData) };
return data;
};
/**
* 对外暴露方法-设置form所有字段值
* @param data 数据对象(JSON格式)
*/
const setData = (data) => {
for (const field of props.fields as any) {
formData[field.name] = data[field.name];
}
};
/**
* 对外暴露方法-重置表单
*/
const reset = () => {
Object.keys(formData).forEach((key) => {
formData[key] = defaultValueHandler(formFieldsMap.get(key));
});
};
const formValidate = async () => {
let validate = false;
await formRef.value.validate().then((success) => {
if (success) {
validate = true;
}
});
return validate;
};
/**
* 对外暴露方法-表单验证
*/
const validate = async () => {
const v = await formValidate();
return v;
};
/**
* 对外暴露方法-设置字段值
* @param fieldName 字段name
* @param value 字段值
*/
const setFieldValue = (fieldName, value) => {
const field = formFieldsMap.get(fieldName);
formData[field.name] = value;
};
/**
* 对外暴露方法-获取字段值
* @param fieldName 字段name
*/
const getFieldValue = (fieldName) => {
return formData[fieldName];
};
/**
* 对外暴露方法-设置form状态
* @param status 状态
*/
const setStatus = (status) => {
formStatus.value = status;
};
/**
* 对外暴露方法-获取form状态
*/
const getStatus = () => {
return toRaw(formStatus.value);
};
/**
* 对外暴露方法-获取当前一行应该显示的元素个数
*/
const getRowColsNum = () => {
return screenColsNumComputed.value;
};
defineExpose({
data: formData,
fieldsMap: formFieldsMap,
getData,
setData,
reset,
validate,
getFieldValue,
setFieldValue,
setStatus,
getStatus,
getRowColsNum,
});
</script>

39
io.sc.platform.core.frontend/src/platform/components/form/elements/WCheckbox.vue

@ -0,0 +1,39 @@
<template>
<div>
<q-checkbox v-show="!hideIfComputed" v-bind="attrs" :rules="rulesComputed" :disable="disableIfComputed"></q-checkbox>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
// import { FormValidators } from '@/platform/components';
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
hideIf: {
type: Function,
default: () => {
return false;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

94
io.sc.platform.core.frontend/src/platform/components/form/elements/WDate.vue

@ -0,0 +1,94 @@
<template>
<div>
<q-input
v-show="!hideIfComputed"
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
v-bind="attrs"
:rules="rulesComputed"
:readonly="readonlyIfComputed"
:disable="disableIfComputed"
>
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
<template #append>
<q-icon :name="IconEnum.日期" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="date" today-btn mask="YYYY-MM-DD">
<div class="row items-center justify-end">
<q-btn v-close-popup label="关闭" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
</template>
<script setup lang="ts">
import { ref, computed, defineProps, useAttrs, watch } from 'vue';
import { FormValidators } from '@/platform/components';
import { IconEnum } from '@/platform/enums';
const date = ref(null);
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
hideIf: {
type: Function,
default: () => {
return false;
},
},
requiredIf: {
type: Function,
default: () => {
return false;
},
},
readonlyIf: {
type: Function,
default: () => {
return false;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
watch(
() => date.value,
(newVal, oldVal) => {
attrs['form-data'][attrs.name] = newVal;
},
);
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
if (!hideIfComputed.value && requiredIfComputed.value) {
rules.push(FormValidators.required());
} else {
rules.splice(rules.length - 1, 1);
}
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const requiredIfComputed = computed(() => {
return props.requiredIf();
});
const readonlyIfComputed = computed(() => {
return props.readonlyIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

78
io.sc.platform.core.frontend/src/platform/components/form/elements/WNumber.vue

@ -0,0 +1,78 @@
<template>
<div>
<q-input
v-show="!hideIfComputed"
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
v-bind="attrs"
type="number"
title=""
:rules="rulesComputed"
:readonly="readonlyIfComputed"
:disable="disableIfComputed"
>
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
</q-input>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
import { FormValidators } from '@/platform/components';
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
precision: { type: Number, default: 0 },
hideIf: {
type: Function,
default: () => {
return false;
},
},
requiredIf: {
type: Function,
default: () => {
return false;
},
},
readonlyIf: {
type: Function,
default: () => {
return false;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
rules.push(FormValidators.maxPrecision(props.precision));
if (!hideIfComputed.value && requiredIfComputed.value) {
rules.push(FormValidators.required());
} else {
rules.splice(rules.length - 1, 1);
}
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const requiredIfComputed = computed(() => {
return props.requiredIf();
});
const readonlyIfComputed = computed(() => {
return props.readonlyIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

76
io.sc.platform.core.frontend/src/platform/components/form/elements/WSelect.vue

@ -0,0 +1,76 @@
<template>
<div>
<q-select
v-show="!hideIfComputed"
emit-value
map-options
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
v-bind="attrs"
:rules="rulesComputed"
:readonly="readonlyIfComputed"
:disable="disableIfComputed"
>
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
</q-select>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
import { FormValidators } from '@/platform/components';
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
hideIf: {
type: Function,
default: () => {
return false;
},
},
requiredIf: {
type: Function,
default: () => {
return false;
},
},
readonlyIf: {
type: Function,
default: () => {
return false;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
if (!hideIfComputed.value && requiredIfComputed.value) {
rules.push(FormValidators.required());
} else {
rules.splice(rules.length - 1, 1);
}
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const requiredIfComputed = computed(() => {
return props.requiredIf();
});
const readonlyIfComputed = computed(() => {
return props.readonlyIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

75
io.sc.platform.core.frontend/src/platform/components/form/elements/WText.vue

@ -0,0 +1,75 @@
<template>
<div>
<q-input
v-show="!hideIfComputed"
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
v-bind="attrs"
type="text"
:rules="rulesComputed"
:readonly="readonlyIfComputed"
:disable="disableIfComputed"
>
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
</q-input>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
import { FormValidators } from '@/platform/components';
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
hideIf: {
type: Function,
default: () => {
return false;
},
},
requiredIf: {
type: Function,
default: () => {
return false;
},
},
readonlyIf: {
type: Function,
default: () => {
return false;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
if (!hideIfComputed.value && requiredIfComputed.value) {
rules.push(FormValidators.required());
} else {
rules.splice(rules.length - 1, 1);
}
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const requiredIfComputed = computed(() => {
return props.requiredIf();
});
const readonlyIfComputed = computed(() => {
return props.readonlyIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

87
io.sc.platform.core.frontend/src/platform/components/form/elements/WTextBtn.vue

@ -0,0 +1,87 @@
<template>
<div>
<q-input
v-show="!hideIfComputed"
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
v-bind="attrs"
type="text"
:rules="rulesComputed"
:readonly="readonlyIfComputed"
:disable="disableIfComputed"
>
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
<template v-if="attrs.button && attrs.buttonPosition === 'prepend'" #prepend>
<q-btn round dense flat v-bind="attrs.button" @click="attrs.button.click" />
</template>
<template v-else-if="attrs.button && attrs.buttonPosition === 'before'" #before>
<q-btn round dense flat v-bind="attrs.button" @click="attrs.button.click" />
</template>
<template v-else-if="attrs.button && attrs.buttonPosition === 'after'" #after>
<q-btn round dense flat v-bind="attrs.button" @click="attrs.button.click" />
</template>
<template v-else-if="attrs.button" #append>
<q-btn round dense flat v-bind="attrs.button" @click="attrs.button.click" />
</template>
</q-input>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
import { FormValidators } from '@/platform/components';
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
hideIf: {
type: Function,
default: () => {
return false;
},
},
requiredIf: {
type: Function,
default: () => {
return false;
},
},
readonlyIf: {
type: Function,
default: () => {
return true;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
if (!hideIfComputed.value && requiredIfComputed.value) {
rules.push(FormValidators.required());
} else {
rules.splice(rules.length - 1, 1);
}
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const requiredIfComputed = computed(() => {
return props.requiredIf();
});
const readonlyIfComputed = computed(() => {
return props.readonlyIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

75
io.sc.platform.core.frontend/src/platform/components/form/elements/WTextarea.vue

@ -0,0 +1,75 @@
<template>
<div>
<q-input
v-show="!hideIfComputed"
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
v-bind="attrs"
type="textarea"
:rules="rulesComputed"
:readonly="readonlyIfComputed"
:disable="disableIfComputed"
>
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
</q-input>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
import { FormValidators } from '@/platform/components';
const attrs = useAttrs();
const inRules = attrs.rules;
const props = defineProps({
hideIf: {
type: Function,
default: () => {
return false;
},
},
requiredIf: {
type: Function,
default: () => {
return false;
},
},
readonlyIf: {
type: Function,
default: () => {
return false;
},
},
disableIf: {
type: Function,
default: () => {
return false;
},
},
});
const rulesComputed = computed(() => {
let rules = inRules || <any>[];
if (!hideIfComputed.value && requiredIfComputed.value) {
rules.push(FormValidators.required());
} else {
rules.splice(rules.length - 1, 1);
}
return rules;
});
const hideIfComputed = computed(() => {
return props.hideIf();
});
const requiredIfComputed = computed(() => {
return props.requiredIf();
});
const readonlyIfComputed = computed(() => {
return props.readonlyIf();
});
const disableIfComputed = computed(() => {
return props.disableIf();
});
</script>

15
io.sc.platform.core.frontend/src/platform/components/grid/EnableIcon.vue

@ -0,0 +1,15 @@
<template>
<div>
<q-icon v-if="value" :name="IconEnum.是状态" color="green" size="sm"> </q-icon>
<q-icon v-else-if="!value && showNoEnable" :name="IconEnum.否状态" color="red" size="sm"> </q-icon>
</div>
</template>
<script lang="ts" setup>
import { IconEnum } from '@/platform/enums';
const props = defineProps({
value: { type: Boolean, default: false }, //
showNoEnable: { type: Boolean, default: false }, //
});
</script>

2311
io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue

File diff suppressed because it is too large

46
io.sc.platform.core.frontend/src/platform/components/index.ts

@ -7,17 +7,31 @@ import WColorInput from './widget/color/WColorInput.vue';
import WColorInputPalette from './widget/color/WColorInputPalette.vue'; import WColorInputPalette from './widget/color/WColorInputPalette.vue';
import WPosition from './widget/position/WPosition.vue'; import WPosition from './widget/position/WPosition.vue';
import WCodemirror from './widget/codemirror/WCodemirror.vue'; import WCodemirror from './widget/codemirror/WCodemirror.vue';
import WSelect from './widget/select/WSelect.vue';
import WListGrid from './grid/WListGrid.vue';
import WTreeGrid from './tree/WTreeGrid.vue'; import WTreeGrid from './tree/WTreeGrid.vue';
import WDialog from './dialog/WDialog.vue'; import WDialog from './dialog/WDialog.vue';
import PlatformDialog from './dialog/PlatformDialog.vue'; import PlatformDialog from './dialog/PlatformDialog.vue';
import PlatformDrawer from './drawer/PlatformDrawer.vue'; import PlatformDrawer from './drawer/PlatformDrawer.vue';
import WDrawer from './drawer/WDrawer.vue';
import PlatformForm from './form/PlatformForm.vue'; import PlatformForm from './form/PlatformForm.vue';
import WForm from './form/WForm.vue';
import WText from './form/elements/WText.vue';
import WNumber from './form/elements/WNumber.vue';
import WTextarea from './form/elements/WTextarea.vue';
import WSelect from './form/elements/WSelect.vue';
import WDate from './form/elements/WDate.vue';
import WCheckbox from './form/elements/WCheckbox.vue';
import WTextBtn from './form/elements/WTextBtn.vue';
import WInfoPanel from './panel/WInfoPanel.vue';
import PlatformGrid from './grid/PlatformGrid.vue'; import PlatformGrid from './grid/PlatformGrid.vue';
import WGrid from './grid/WGrid.vue';
import PlatformGridTdDrag from './grid/PlatformGridTdDrag.vue'; import PlatformGridTdDrag from './grid/PlatformGridTdDrag.vue';
import WToolbar from './toolbar/WToolbar.vue';
export default { export default {
install: (app: App) => { install: (app: App) => {
app.component('WPlatformPage', WPlatformPage); app.component('WPlatformPage', WPlatformPage);
@ -29,15 +43,28 @@ export default {
app.component('WPosition', WPosition); app.component('WPosition', WPosition);
app.component('WCodemirror', WCodemirror); app.component('WCodemirror', WCodemirror);
app.component('WSelect', WSelect); app.component('WSelect', WSelect);
app.component('WListGrid', WListGrid);
app.component('WTreeGrid', WTreeGrid); app.component('WTreeGrid', WTreeGrid);
app.component('WDialog', WDialog); app.component('WDialog', WDialog);
app.component('PlatformDialog', PlatformDialog); app.component('PlatformDialog', PlatformDialog);
app.component('WDrawer', WDrawer);
app.component('PlatformDrawer', PlatformDrawer); app.component('PlatformDrawer', PlatformDrawer);
app.component('WForm', WForm);
app.component('WText', WText);
app.component('WNumber', WNumber);
app.component('WTextarea', WTextarea);
app.component('WDate', WDate);
app.component('WCheckbox', WCheckbox);
app.component('WTextBtn', WTextBtn);
app.component('WInfoPanel', WInfoPanel);
app.component('PlatformForm', PlatformForm); app.component('PlatformForm', PlatformForm);
app.component('PlatformGrid', PlatformGrid); app.component('PlatformGrid', PlatformGrid);
app.component('WGrid', WGrid);
app.component('PlatformGridTdDrag', PlatformGridTdDrag); app.component('PlatformGridTdDrag', PlatformGridTdDrag);
app.component('WToolbar', WToolbar);
}, },
}; };
@ -50,14 +77,23 @@ export {
WPosition, WPosition,
WCodemirror, WCodemirror,
WSelect, WSelect,
WListGrid,
WTreeGrid, WTreeGrid,
WDialog, WDialog,
PlatformDialog, PlatformDialog,
PlatformDrawer, PlatformDrawer,
WDrawer,
PlatformForm, PlatformForm,
WForm,
WText,
WNumber,
WTextarea,
WDate,
WCheckbox,
WTextBtn,
PlatformGrid, PlatformGrid,
WGrid,
PlatformGridTdDrag, PlatformGridTdDrag,
WToolbar,
}; };
export { PlatformIconEnum } from './utils'; export { PlatformIconEnum } from './utils';
@ -79,3 +115,5 @@ export { extractTableProps } from './utils';
export { extractTableColumnsProps } from './utils'; export { extractTableColumnsProps } from './utils';
export { getQueryFormColsNumberByScreen } from './utils'; export { getQueryFormColsNumberByScreen } from './utils';
export { arrayToMap } from './utils'; export { arrayToMap } from './utils';
export { FormValidators } from './utils';

95
io.sc.platform.core.frontend/src/platform/components/panel/WInfoPanel.vue

@ -0,0 +1,95 @@
<template>
<div>
<q-markup-table separator="cell" flat bordered wrap-cells v-bind="attrs">
<tbody>
<template v-for="(trItem, trIndex) in tableComputed" :key="trIndex">
<tr class="q-tr--no-hover">
<template v-for="(tdItem, tdIndex) in trItem as any" :key="tdIndex">
<td :class="labelAlignClassComputed">{{ tdItem.label }}</td>
<td :class="valueAlignClassComputed">
<template v-if="tdItem.value && typeof tdItem.value === 'object' && tdItem.value.type && tdItem.value._vuecomp_">
<component :is="tdItem.value.type" v-bind="tdItem.value.props"></component>
</template>
<template v-else>
{{ tdItem.value }}
</template>
</td>
</template>
</tr>
</template>
</tbody>
</q-markup-table>
</div>
</template>
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { getCssVar } from 'quasar';
import { Environment } from '@/platform';
const attrs = useAttrs();
const gc = Environment.getConfigure();
const darkBgColor = getCssVar('dark');
const bgColor = gc.theme.dark ? darkBgColor : gc.theme?.grid?.headBgColor || '#f5f7fa';
const props = defineProps({
columnNum: { type: Number, default: 1 },
labelAlign: { type: String, default: 'left' },
valueAlign: { type: String, default: 'left' },
info: {
type: Array,
default: () => {
return [];
},
},
});
const tableComputed = computed(() => {
const table = <any>[];
let tmp = <any>[];
props.info.forEach((item, index) => {
if (tmp.length < props.columnNum) {
tmp.push(item);
} else {
table.push(tmp);
tmp = [];
tmp.push(item);
}
});
table.push(tmp);
return table;
});
const labelAlignClassComputed = computed(() => {
let className = '';
switch (props.labelAlign) {
case 'right':
className = 'text-right';
break;
case 'center':
className = 'text-center';
break;
default:
className = 'text-left';
}
return className;
});
const valueAlignClassComputed = computed(() => {
let className = '';
switch (props.valueAlign) {
case 'right':
className = 'text-right';
break;
case 'center':
className = 'text-center';
break;
default:
className = 'text-left';
}
return className;
});
</script>
<style scoped>
.q-table td:nth-child(odd) {
background-color: v-bind(bgColor);
}
</style>

57
io.sc.platform.core.frontend/src/platform/components/toolbar/ChildrenBtn.vue

@ -0,0 +1,57 @@
<template>
<q-item clickable :disable="button[0].enableIf ? !button[0].enableIf(gridProp.gridSelected) : false">
<q-item-section>
<q-item-label><q-icon v-if="button[0].icon" :name="button[0].icon" size="20px"></q-icon> {{ button[0].label }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="keyboard_arrow_right" />
</q-item-section>
<q-menu anchor="top end" self="top start">
<q-list>
<template v-for="(childrenBtn, index) in button" :key="index">
<template v-if="index === 0 && !childrenBtn.click"></template>
<q-separator v-else-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" />
<ChildrenBtn v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0" :button="childrenBtn"></ChildrenBtn>
<template v-else>
<q-item
v-close-popup
clickable
:disable="childrenBtn.enableIf ? !childrenBtn.enableIf(gridProp.gridSelected) : false"
@click="buttonClick(childrenBtn)"
>
<q-item-section>
<q-item-label><q-icon v-if="childrenBtn.icon" :name="childrenBtn.icon" size="20px"></q-icon> {{ childrenBtn.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</template>
</q-list>
</q-menu>
</q-item>
</template>
<script setup lang="ts">
const props = defineProps({
button: {
type: Object,
default: () => {
return [];
},
},
gridProp: {
type: Object,
default: () => {
return {
gridSelected: [],
gridRefs: {},
};
},
},
buttonClick: {
type: Function,
default: () => {
return () => {};
},
},
});
</script>

284
io.sc.platform.core.frontend/src/platform/components/toolbar/WToolbar.vue

@ -0,0 +1,284 @@
<template>
<div>
<div
ref="containerRef"
:class="'flex ' + (align === 'left' ? 'justify-start' : align === 'center' ? 'justify-center' : 'justify-end') + ' gap-x-[' + props.xGap + 'px]'"
>
<!-- buttons -->
<template v-for="(btn, index) in baseActions" :key="'button_' + index">
<q-separator v-if="typeof btn.data === 'string' && btn.data === 'separator'" vertical class="class-action-item" />
<q-btn-dropdown
v-else-if="Array.isArray(btn.data) && btn.data.length > 0"
unelevated
outline
v-bind="btn.data[0]"
:split="btn.data[0].click ? true : false"
:disable="btn.data[0].enableIf ? !btn.data[0].enableIf(gridProp.gridSelected) : false"
class="class-action-item"
@click="buttonClick(btn.data[0])"
>
<q-list>
<template v-for="(childrenBtn, childrenIndex) in btn.data" :key="'button_c_' + childrenIndex">
<template v-if="childrenIndex === 0"></template>
<template v-else>
<q-separator v-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" />
<ChildrenBtn
v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0"
:button="childrenBtn"
:grid-prop="gridProp"
:button-click="buttonClick"
></ChildrenBtn>
<q-item
v-else
v-close-popup
clickable
:disable="childrenBtn.enableIf ? !childrenBtn.enableIf(gridProp.gridSelected) : false"
@click="buttonClick(childrenBtn)"
>
<q-item-section>
<q-item-label><q-icon v-if="childrenBtn.icon" :name="childrenBtn.icon" size="20px"></q-icon> {{ childrenBtn.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</template>
</q-list>
</q-btn-dropdown>
<q-btn
v-else
:disable="btn.data.enableIf ? !btn.data.enableIf(gridProp.gridSelected) : false"
no-wrap
outline
v-bind="btn.data"
class="class-action-item"
@click="buttonClick(btn.data)"
/>
</template>
<!-- moreActions -->
<q-btn-dropdown v-if="moreActions && moreActions.length > 0" unelevated outline :label="$t('more')" class="class-action-item">
<q-list>
<template v-for="(childrenBtn, childrenIndex) in moreActions" :key="'moreAction_' + childrenIndex">
<q-separator v-if="typeof childrenBtn.data === 'string' && childrenBtn.data === 'separator'" />
<ChildrenBtn
v-else-if="Array.isArray(childrenBtn.data) && childrenBtn.data.length > 0"
:button="childrenBtn.data"
:grid-prop="gridProp"
:button-click="buttonClick"
></ChildrenBtn>
<q-item
v-else
v-close-popup
clickable
:disable="childrenBtn.data.enableIf ? !childrenBtn.data.enableIf(gridProp.gridSelected) : false"
@click="buttonClick(childrenBtn.data)"
>
<q-item-section>
<q-item-label
><q-icon v-if="childrenBtn.data.icon" :name="childrenBtn.data.icon" size="20px"></q-icon> {{ childrenBtn.data.label }}</q-item-label
>
</q-item-section>
</q-item>
</template>
</q-list>
</q-btn-dropdown>
</div>
<q-resize-observer @resize="onResize" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue';
import { Tools } from '@/platform/utils';
import ChildrenBtn from './ChildrenBtn.vue';
const props = defineProps({
xGap: { type: Number, default: 4 }, // x
noIcon: { type: Boolean, default: false }, // icon
align: { type: String, default: 'right' }, //
buttons: {
type: Array,
default: () => {
return [];
},
},
gridProp: {
type: Object,
default: () => {
return {
gridSelected: [],
gridRefs: {},
};
},
},
});
const containerRef = ref();
// name JSON
const buttonsJson = {};
const extractButton = (btns: any) => {
for (const btn of btns) {
if (Array.isArray(btn)) {
extractButton(btn);
} else if (typeof btn === 'object') {
buttonsJson[btn.name] = { ...btn };
if (props.noIcon) {
buttonsJson[btn.name].icon = undefined;
}
}
}
};
extractButton(props.buttons);
// //
// const handleChildren = (parent, children, arr) => {
// for (const item of arr) {
// if (Array.isArray(item)) {
// const other = item.splice(1);
// const children_ = <any>[];
// handleChildren(item[0].name, children_, other);
// children.push({ parent, children: children_, button: buttonsJson[item[0].name] });
// } else if (typeof item === 'object') {
// children.push({ parent, children: null, button: buttonsJson[item.name] });
// } else if (typeof item === 'string' && item === 'separator') {
// //
// if (children.length > 0 && children[children.length - 1].button !== 'separator') {
// children.push({ parent, children: null, button: 'separator' });
// }
// }
// }
// };
// const buttons_ = <any>[];
// for (const btn of props.buttons) {
// if (Array.isArray(btn)) {
// const other = btn.splice(1);
// const children = <any>[];
// handleChildren(btn[0].name, children, other);
// buttons_.push({ parent: null, children: children, button: buttonsJson[btn[0].name] });
// } else if (typeof btn === 'object') {
// buttons_.push({ parent: null, children: null, button: buttonsJson[btn.name] });
// } else if (typeof btn === 'string' && btn === 'separator') {
// //
// if (buttons_.length > 0 && buttons_[buttons_.length - 1].button !== 'separator') {
// buttons_.push({ parent: null, children: null, button: 'separator' });
// }
// }
// }
const buttons_ = <any>[];
const handleChildrenSeparator = (arr) => {
const tempArr = [];
for (let i = 0; i < arr.length; i++) {
const btn = arr[i];
if (i === 0 && typeof btn === 'string' && btn === 'separator') {
//
continue;
}
if (typeof btn === 'string' && btn === 'separator' && tempArr.length > 0 && tempArr[tempArr.length - 1] === btn) {
//
continue;
}
if (Array.isArray(btn) && btn.length > 0) {
const handleResult = handleChildrenSeparator(btn);
if (handleResult && handleResult.length > 0) {
tempArr.push(handleResult);
}
} else if (typeof btn === 'string' && btn === 'separator') {
tempArr.push(btn);
} else {
tempArr.push(buttonsJson[btn.name]);
}
}
return tempArr;
};
for (let i = 0; i < props.buttons.length; i++) {
const btn = props.buttons[i];
if (i === 0 && typeof btn === 'string' && btn === 'separator') {
//
continue;
}
if (typeof btn === 'string' && btn === 'separator' && buttons_.length > 0 && buttons_[buttons_.length - 1].data === btn) {
//
continue;
}
if (Array.isArray(btn) && btn.length > 0) {
buttons_.push({ data: handleChildrenSeparator(btn) });
} else if (typeof btn === 'string' && btn === 'separator') {
buttons_.push({ data: btn });
} else {
buttons_.push({ data: buttonsJson[btn.name] });
}
}
const baseActions = ref(buttons_);
const moreActions = ref([]);
const isActionWidthInitializedRef = ref(false);
const moreActionWidth = 100;
const onResize = (size) => {
if (Tools.isUndefinedOrNull(containerRef.value)) {
return;
}
if (!isActionWidthInitializedRef.value) {
const nodes = containerRef.value.getElementsByClassName('class-action-item');
for (let i = 0; i < buttons_.length; i++) {
buttons_[i].width = nodes[i].clientWidth;
}
isActionWidthInitializedRef.value = true;
}
const _baseActions = [];
const _moreActions = [];
const length = buttons_.length;
let availableWidth = size.width;
let width = 0;
let index = 0;
for (; index < length; index++) {
if (width + buttons_[index].width > availableWidth) {
availableWidth -= moreActionWidth;
while (width > availableWidth) {
index--;
width -= buttons_[index].width;
_baseActions.pop();
}
break;
} else {
_baseActions.push(buttons_[index]);
width += buttons_[index].width;
width += props.xGap;
}
}
for (; index < length; index++) {
_moreActions.push(buttons_[index]);
}
baseActions.value = _baseActions;
moreActions.value = _moreActions;
};
const buttonClick = async (button) => {
let beforeResult = true;
const context = {};
if (button.beforeClick) {
beforeResult = await button.beforeClick(props.gridProp.gridSelected, context, props.gridProp.gridRefs);
}
if (beforeResult && button.click) {
await button.click(props.gridProp.gridSelected, context, button._click, props.gridProp.gridRefs);
if (button.afterClick) {
nextTick(() => {
button.afterClick(props.gridProp.gridSelected, context, props.gridProp.gridRefs);
});
}
}
};
</script>
<style scoped lang="css">
.q-btn.disabled {
opacity: 0.7 !important;
color: #a8a8a8;
}
.q-item.disabled {
opacity: 0.6 !important;
color: #a8a8a8;
}
</style>

58
io.sc.platform.core.frontend/src/platform/components/utils/FormValidators.ts

@ -0,0 +1,58 @@
/**
* Form表单元素验证器
*/
export class FormValidators {
/**
*
* @returns
*/
public static required(msg: string) {
return (val) => {
if (val === null || val === undefined) {
return msg || '必填项未填写';
}
if (typeof val === 'string') {
return val !== '' || msg || '必填项未填写';
} else if (Array.isArray(val)) {
return val.length > 0 || msg || '必填项未填写';
} else {
return true;
}
};
}
/**
*
* @param min
* @param max
* @returns
*/
public static lengthRange(min: number, max: number, msg: string) {
return (val) => {
const tmp = String(val);
if (tmp === null || tmp === '' || (tmp.length >= min && tmp.length <= max)) {
return true;
} else {
return msg || '长度不符合要求(' + min + '-' + max + ')';
}
};
}
/**
*
* @param precision
* @returns
*/
public static maxPrecision(precision: number, msg: string) {
return (val) => {
const tmp = String(val);
if (val === null || tmp === '' || tmp.indexOf('.') === -1 || tmp.substring(tmp.indexOf('.') + 1).length <= precision) {
return true;
} else if (precision === 0) {
return '只能输入整数';
} else {
return msg || '最大允许输入的小数位:' + precision;
}
};
}
}

8
io.sc.platform.core.frontend/src/platform/components/utils/componentComm.ts

@ -254,7 +254,7 @@ function columnStyle(item: any) {
style = item.style; style = item.style;
} }
if (Object.hasOwnProperty.call(item, 'width')) { if (Object.hasOwnProperty.call(item, 'width')) {
item.style = `min-width: ` + item.width + `px; max-width: ` + item.width + `px;` + style; item.style = `min-width: ` + item.width + `px; width: ` + item.width + `px;max-width: ` + item.width + `px;` + style;
delete item.width; delete item.width;
if (Object.hasOwnProperty.call(item, 'classes')) { if (Object.hasOwnProperty.call(item, 'classes')) {
@ -282,11 +282,11 @@ function columnChildrenHandler(item: any, gridColumns: any) {
*/ */
export function extractTableColumnsProps(props: any) { export function extractTableColumnsProps(props: any) {
const gridColumns = <any>[]; const gridColumns = <any>[];
if (props.tableColumns && props.tableColumns.length > 0) { if (props.columns && props.columns.length > 0) {
if (props.tableShowSortNo) { if (props.showSortNo) {
gridColumns.push({ name: '_sortNo_', align: 'center', label: '序号', field: '_sortNo_' }); gridColumns.push({ name: '_sortNo_', align: 'center', label: '序号', field: '_sortNo_' });
} }
props.tableColumns.forEach((item: any) => { props.columns.forEach((item: any) => {
columnChildrenHandler(item, gridColumns); columnChildrenHandler(item, gridColumns);
}); });
return gridColumns; return gridColumns;

2
io.sc.platform.core.frontend/src/platform/components/utils/index.ts

@ -17,3 +17,5 @@ export { extractTableProps } from './componentComm';
export { extractTableColumnsProps } from './componentComm'; export { extractTableColumnsProps } from './componentComm';
export { getQueryFormColsNumberByScreen } from './componentComm'; export { getQueryFormColsNumberByScreen } from './componentComm';
export { arrayToMap } from './componentComm'; export { arrayToMap } from './componentComm';
export { FormValidators } from './FormValidators';

2
io.sc.platform.core.frontend/src/platform/index.ts

@ -105,7 +105,7 @@ export {
WPosition, WPosition,
WCodemirror, WCodemirror,
WSelect, WSelect,
WListGrid, // WListGrid,
WTreeGrid, WTreeGrid,
PlatformDialog, PlatformDialog,
PlatformDrawer, PlatformDrawer,

94
io.sc.platform.core.frontend/src/routes/routes.json

@ -48,15 +48,99 @@
} }
}, },
{ {
"name": "route.testcase.table", "name": "route.testcase.likm.dialog",
"path": "testcase/table", "path": "testcase/likm/dialog",
"parent": "/", "parent": "/",
"priority": 0, "priority": 0,
"component": "component.testcase.table", "component": "component.testcase.likmDialog",
"componentPath": "@/views/Table.vue", "componentPath": "@/views/likm/Dialog.vue",
"redirect": null, "redirect": null,
"meta": { "meta": {
"permissions": ["/testcase/table/**/*"] "permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.drawer",
"path": "testcase/likm/drawer",
"parent": "/",
"priority": 0,
"component": "component.testcase.likmDrawer",
"componentPath": "@/views/likm/Drawer.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.form",
"path": "testcase/likm/form",
"parent": "/",
"priority": 0,
"component": "component.testcase.likmForm",
"componentPath": "@/views/likm/Form.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.infoPanel",
"path": "testcase/likm/infoPanel",
"parent": "/",
"priority": 0,
"component": "component.testcase.infoPanel",
"componentPath": "@/views/likm/InfoPanel.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.grid",
"path": "testcase/likm/grid",
"parent": "/",
"priority": 0,
"component": "component.testcase.grid",
"componentPath": "@/views/likm/Grid.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.toolbar",
"path": "testcase/likm/toolbar",
"parent": "/",
"priority": 0,
"component": "component.testcase.toolbar",
"componentPath": "@/views/likm/Toolbar.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.quasarGrid",
"path": "testcase/likm/quasarGrid",
"parent": "/",
"priority": 0,
"component": "component.testcase.quasarGrid",
"componentPath": "@/views/likm/QuasarGrid.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
}
},
{
"name": "route.testcase.likm.gridLayout",
"path": "testcase/likm/gridLayout",
"parent": "/",
"priority": 0,
"component": "component.testcase.gridLayout",
"componentPath": "@/views/likm/GridLayout.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/treeGrid/**/*"]
} }
} }
] ]

30
io.sc.platform.core.frontend/src/views/Form1.vue

@ -0,0 +1,30 @@
<template>
<div>
<w-form :fields="fields" :cols-num-auto="false" :cols-num="3"></w-form>
</div>
</template>
<script setup lang="ts">
const fields = [
{
label: '姓名',
name: 'name',
type: 'text',
required: true,
colspan: 2,
button: {
round: false,
icon: 'home',
label: '选择',
click: () => {
console.info('11111');
},
},
},
{
label: '年龄',
name: 'age',
type: 'number',
required: true,
},
];
</script>

4
io.sc.platform.core.frontend/src/views/Grid1.vue

@ -0,0 +1,4 @@
<template>
<div>2222</div>
</template>
<script setup lang="ts"></script>

13
io.sc.platform.core.frontend/src/views/likm/A.vue

@ -0,0 +1,13 @@
<template>
<q-input ref="aaaref" :model-value="a" label="dddd"></q-input>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const aaaref = ref();
const a = ref('');
onMounted(() => {
aaaref.value.focus();
console.info('aaaref====', aaaref);
});
</script>

35
io.sc.platform.core.frontend/src/views/likm/Dialog.vue

@ -0,0 +1,35 @@
<template>
<div>
<w-dialog
ref="dialogRef"
:buttons="[
{
label: '测试',
click: aaaaa,
},
]"
@hide="aaaaa"
>
<q-splitter v-model="aa" style="height: 100%">
<template #before> 11111 </template>
<template #after> 22222 </template>
</q-splitter>
<template #buttons> <q-btn label="xxx"></q-btn> </template
></w-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
const dialogRef = ref();
const aa = ref(50);
const aaaaa = (e) => {
console.info('dddddddddddddd', e);
};
onMounted(() => {
console.info('dialogRef====', dialogRef);
dialogRef.value.show();
});
</script>

34
io.sc.platform.core.frontend/src/views/likm/Drawer.vue

@ -0,0 +1,34 @@
<template>
<div>
<q-btn label="弹出" @click="click"></q-btn>
<w-drawer
ref="drawerRef"
title="xxx"
:maximized="false"
:buttons="[
{
label: '测试',
},
]"
>
1111
<template #buttons> <q-btn label="xxx"></q-btn> </template>
</w-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { IconEnum } from '@/platform/enums';
const drawerRef = ref();
const aa = ref(50);
const aaaaa = (e) => {
console.info('dddddddddddddd', e);
};
const click = () => {
drawerRef.value.show();
};
onMounted(() => {});
</script>

152
io.sc.platform.core.frontend/src/views/likm/Form.vue

@ -0,0 +1,152 @@
<template>
<div>
<w-form ref="formRef" :fields="aaaa.fields" :cols-x-gap="8"> </w-form>
<q-btn label="提交" @click="submit"></q-btn>&nbsp; <q-btn label="重置" @click="reset"></q-btn>&nbsp;
<q-btn label="初始化值" @click="setValue"></q-btn>&nbsp;
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { FormValidators } from '@/platform/components';
const formRef = ref();
const aaaa = {
fields: [
{
label: '姓名',
name: 'name',
type: 'w-text',
// maxlength: 10,
rules: [FormValidators.lengthRange(5, 10, '22222')],
defaultValue: '123',
colspan: 2,
requiredIf: () => true,
'onUpdate:modelValue': () => {},
onFocus: () => {
// console.info('33333333');
},
},
{
label: '机构',
name: 'org',
type: 'w-text-btn',
requiredIf: () => true,
// buttonPosition: 'prepend',
button: {
icon: 'home',
click: () => {
console.info('点击!!');
},
},
},
{
label: '年龄',
name: 'age',
type: 'w-number',
defaultValue: 111,
precision: 0,
requiredIf: () => true,
},
{
label: '出生日期',
name: 'cs',
type: 'w-date',
requiredIf: () => true,
},
{
label: '爱好',
name: 'ah',
type: 'w-text',
requiredIf: () => true,
},
{
label: '性别',
name: 'sex',
type: 'w-select',
options: [
{
label: '男',
value: 1,
},
{
label: '女',
value: 0,
},
],
requiredIf: () => true,
'onUpdate:modelValue': (value) => {
if (value === 1) {
formRef.value.fieldsMap.get('addr').label = '地址2222';
} else {
formRef.value.fieldsMap.get('addr').label = '地址3333';
}
},
},
{
label: '地址',
name: 'addr',
rows: 2,
colspan: 'full',
type: 'w-textarea',
hideIf: () => {
if (formRef.value) {
if (formRef.value.data.sex && formRef.value.data.sex === 1) {
return false;
} else {
return false;
}
}
return false;
},
requiredIf: () => true,
// readonlyIf: () => {
// return true;
// },
disableIf: () => {
return false;
},
},
{
label: '是否可用1',
name: 'ky1',
colsFirst: true,
colspan: 2,
type: 'w-checkbox',
// disableIf: () => {
// return true;
// },
},
{
label: '是否可用2',
name: 'ky2',
type: 'w-checkbox',
},
{
label: '是否可用3',
name: 'ky3',
type: 'w-checkbox',
},
],
};
const submit = async () => {
aaaa.fields[1].required = false;
const validateResult = await formRef.value.validate();
console.info('表单验证结果:', validateResult);
console.info('数据:', formRef.value.getData());
};
const reset = () => {
formRef.value.reset();
};
const setValue = () => {
const data = {
name: '张三',
age: 18,
cs: '2023-12-25',
ah: '唱、跳、篮球',
ky: true,
sex: 1,
addr: '上海市',
};
formRef.value.setData(data);
};
</script>

220
io.sc.platform.core.frontend/src/views/likm/Grid.vue

@ -0,0 +1,220 @@
<template>
<div>
<w-grid
:title="testGrid.title"
:row-drag="true"
:row-key="testGrid.rowKey"
:auto-load-data="testGrid.autoLoadData"
:get-data-url="testGrid.tableDataUrl"
:show-sort-no="testGrid.tableShowSortNo"
:columns="testGrid.tableColumns"
:left-column-sticky-number="testGrid.tableLeftColumnStickyNumber"
:column-title-dense="true"
:toolbar="testGrid.toolbar"
:query-form="testGrid.queryForm"
:table-pagination="testGrid.tablePagination"
:add-edit="testGrid.addEdit"
:view="testGrid.view"
@selection="selection"
@row-click="rowClick"
@row-db-click="rowDbClick"
></w-grid>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';
import { axios, Environment } from '@/platform';
import EnableIcon from '@/platform/components/grid/EnableIcon.vue';
import { IconEnum } from '@/platform/enums';
const selection = (details) => {
console.info('details====', details);
};
const rowClick = (evt, row, index) => {
console.info('row====', row);
};
const rowDbClick = (evt, row, index) => {
console.info('row1====', row);
};
const testGrid = {
hideBottom: false,
autoLoadData: true,
tableLeftColumnStickyNumber: 0,
title: '用户列表',
rowKey: 'id',
tableDataUrl: Environment.apiContextPath('api/system/user'),
tablePagination: {
sortBy: 'lastModifyDate',
descending: true,
reqPageStart: 0,
rowsPerPage: 10,
},
toolbar: {
buttons: [
['query', 'separator', 'moreQuery'],
'reset',
'refresh',
'separator',
{
name: 'custBtn',
extend: 'add',
icon: undefined,
label: '自定义按钮',
enableIf: (selected) => {
if (selected && selected.length > 0) {
return true;
}
return false;
},
// beforeClick: (selected, context, gridRefs) => {
// console.info('before');
// context.aaa = '111';
// },
click: (selected, context, _click, gridRefs) => {
_click();
},
afterClick: (selected, context, gridRefs) => {
gridRefs.addEditFormRef.setFieldValue('userName', '李四');
},
},
[
{
name: 'op',
icon: 'difference',
label: '操作',
},
'add',
'edit',
'clone',
'remove',
'separator',
'view',
'export',
],
'separator',
],
},
tableShowSortNo: true,
queryForm: {
rowNum: 1,
fields: [
{ label: '登录名', name: 'loginName', type: 'w-text' },
{ label: '用户名', name: 'userName', type: 'w-text' },
{ label: '描述', name: 'description', type: 'w-text' },
{ label: '是否可用', name: 'enable', type: 'w-select' },
// { label: '', name: 'email', type: 'w-text' },
// { label: '', name: 'phone', type: 'w-text' },
// { label: '', name: 'mobile', type: 'w-number' },
// { label: '', name: 'lastModifier', type: 'w-text' },
// { label: '', name: 'lastModifyDate', type: 'w-date' },
],
},
tableColumns: [
{
name: 'info',
label: '用户信息',
childrenColumns: [
{ name: 'loginName', label: '登录名', align: 'right' },
{ name: 'userName', label: '用户名' },
],
},
{
name: 'lxxx',
label: '联系方式',
childrenColumns: [
{ name: 'email', label: '邮箱地址' },
{
name: 'tx',
label: '通讯',
childrenColumns: [
{ name: 'phone', label: '电话' },
{ name: 'mobile', label: '手机号' },
],
},
{ name: 'qq', label: 'QQ' },
],
},
{ name: 'description', label: '描述' },
{
name: 'enable',
label: '是否可用',
align: 'center',
format: (val, row) => {
// return {
// _vuecomp_: true,
// type: EnableIcon,
// props: {
// value: val,
// showNoEnable: true,
// },
// };
// return {
// _vuecomp_: true,
// type: 'q-chip',
// props: {
// label: val ? '' : '',
// icon: val ? IconEnum. : IconEnum.,
// color: val ? 'green' : 'red',
// },
// };
return {
_vuecomp_: true,
type: 'q-icon',
props: {
name: val ? IconEnum.是状态 : IconEnum.否状态,
color: val ? 'green' : 'red',
size: 'sm',
},
};
// return {
// _vuecomp_: true,
// type: 'w-text',
// props: {
// name: 'aaa',
// label: '',
// },
// };
},
},
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
addEdit: {
dialog: {},
form: {
colsNum: 1,
fields: [
{ label: '登录名', name: 'loginName', type: 'w-text' },
{ label: '用户名', name: 'userName', type: 'w-text' },
{ label: '密码', name: 'password', type: 'w-text' },
{ label: '是否可用1111', name: 'enable', type: 'w-checkbox' },
],
},
},
view: {
infoPanel: {
columnNum: 2,
fields: [
{ name: 'id', label: '主键' },
{ name: 'loginName', label: '登录名' },
{ name: 'userName', label: '用户名' },
{ name: 'description', label: '描述' },
{
name: 'enable',
label: '是否可用',
},
{ name: 'email', label: '邮箱地址' },
{ name: 'phone', label: '电话' },
{ name: 'mobile', label: '手机号' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
},
},
};
onMounted(() => {
// testGridRef.value.replaceRowsFun([{ typeName: '', typeDesc: '', lastModifier: 'admin', lastModifyDate: '2023-12-26' }]);
});
</script>

308
io.sc.platform.core.frontend/src/views/likm/GridLayout.vue

@ -0,0 +1,308 @@
<template>
<q-splitter :model-value="70" class="w-full h-full">
<template #before>
<w-grid
ref="userGridRef"
:title="userConfigure.tableTitle"
:row-key="userConfigure.tableRowKey"
:auto-load-data="userConfigure.tableInitLoadData"
:get-data-url="userConfigure.tableDataUrl"
:show-sort-no="false"
:columns="userConfigure.tableColumns"
:left-column-sticky-number="userConfigure.tableLeftColumnStickyNumber"
:toolbar="userConfigure.toolbar"
:query-form="userConfigure.queryForm"
:table-pagination="userConfigure.tablePagination"
@row-click="userConfigure.rowClickFun"
></w-grid>
</template>
<template #after>
<q-tabs v-model="selectedTabRef" inline-label align="left" :breakpoint="0">
<q-tab name="role" icon="bi-people" :label="$t('role')" />
<q-tab name="org" icon="bi-diagram-3" :label="$t('org')" />
</q-tabs>
<q-tab-panels v-model="selectedTabRef" animated swipeable keep-alive>
<q-tab-panel name="role" class="p-0">
<w-grid
ref="roleGridRef"
selection="multiple"
:hide-bottom="false"
:title="roleConfigure.tableTitle"
:row-key="roleConfigure.tableRowKey"
:auto-load-data="roleConfigure.tableInitLoadData"
:get-data-url="roleConfigure.tableDataUrl"
:show-sort-no="false"
:columns="roleConfigure.tableColumns"
:left-column-sticky-number="roleConfigure.tableLeftColumnStickyNumber"
:toolbar="roleConfigure.toolbar"
:table-pagination="roleConfigure.tablePagination"
></w-grid>
</q-tab-panel>
<q-tab-panel name="org"> 11111 </q-tab-panel>
</q-tab-panels>
</template>
</q-splitter>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { Environment, axios } from '@/platform';
const { t } = useI18n();
const userGridRef = ref();
const roleGridRef = ref();
const orgTreeGridRef = ref();
const selectRoleDialog = ref();
const changePasswordDialog = ref();
const selectedTabRef = ref('role');
const userConfigure = {
queryForm: {
fields: [
{ label: t('loginName'), name: 'loginName', type: 'w-text' },
{ label: t('userName'), name: 'userName', type: 'w-text' },
{
label: t('enable'),
name: 'enable',
type: 'w-select',
options: [
{ value: true, label: '是' },
{ value: false, label: '否' },
],
},
{
label: t('dataComeFrom'),
name: 'dataComeFrom',
type: 'w-select',
options: [
{ value: 'MANUAL', label: t('io.sc.platform.orm.api.enums.DataComeFrom.MANUAL') },
{ value: 'AUTO', label: t('io.sc.platform.orm.api.enums.DataComeFrom.AUTO') },
],
},
],
},
hideBottom: false,
tableInitLoadData: true,
tableLeftColumnStickyNumber: 0,
tableTitle: t('system.user.gridTitle'),
tableRowKey: 'id',
tableDataUrl: Environment.apiContextPath('/api/system/user'),
tablePagination: {
sortBy: 'lastModifyDate',
descending: true,
reqPageStart: 0,
rowsPerPage: 10,
},
toolbar: {
buttons: [
'query',
'reset',
'separator',
'refresh',
'add',
'edit',
'remove',
'separator',
'view',
'separator',
{
name: 'setPassword',
label: t('system.user.action.setPassword'),
icon: 'bi-shield-exclamation',
enableIf: function () {},
click: function () {
changePasswordDialog.value.show();
},
},
],
},
tableColumns: [
{ width: 100, name: 'loginName', label: t('loginName') },
{ width: 100, name: 'userName', label: t('userName') },
{ width: 100, name: 'enable', label: t('isEnable'), format: (value) => (value ? t('yes') : t('no')) },
{ width: 100, name: 'dataComeFrom', label: t('dataComeFrom') },
{ width: 100, name: 'accountExpired', label: t('accountExpired'), format: (value) => (value ? t('yes') : t('no')) },
{ width: 100, name: 'accountLocked', label: t('accountLocked'), format: (value) => (value ? t('yes') : t('no')) },
{ width: 120, name: 'credentialsExpired', label: t('credentialsExpired'), format: (value) => (value ? t('yes') : t('no')) },
{ width: 110, name: 'lastModifier', label: t('lastModifier') },
{ width: 115, name: 'lastModifyDate', label: t('lastModifyDate') },
],
addFormProps: {
dialogInitWidth: '50%',
dialogInitHeight: '90%',
formColsNumber: 1,
formColsAuto: false,
formFields: [
{ modelName: 'loginName', label: t('loginName'), type: 'text', required: true },
{ modelName: 'userName', label: t('userName'), type: 'text', required: true },
{ modelName: 'password', label: t('password'), type: 'password' },
{ modelName: 'confirmPassword', label: t('confirmPassword'), type: 'password' },
{ modelName: 'description', label: t('description'), type: 'textarea' },
{ modelName: 'email', label: t('email'), type: 'text' },
{ modelName: 'phone', label: t('phone'), type: 'text' },
{ modelName: 'mobile', label: t('mobile'), type: 'text' },
{ modelName: 'weixin', label: t('weixin'), type: 'text' },
{ modelName: 'qq', label: t('qq'), type: 'text' },
{ modelName: 'enable', label: t('enable'), type: 'checkbox', defaultValue: true },
{ modelName: 'accountExpired', label: t('accountExpired'), type: 'checkbox' },
{ modelName: 'accountLocked', label: t('accountLocked'), type: 'checkbox' },
{ modelName: 'credentialsExpired', label: t('credentialsExpired'), type: 'checkbox' },
],
},
rowClickFun: (evt, row, index) => {
if (roleGridRef.value) {
axios.get(Environment.apiContextPath('/api/system/role/queryRolesByUser?userId=') + row.id).then((response) => {
roleGridRef.value.replaceRowsFun(response.data.content);
});
}
if (orgTreeGridRef.value) {
axios.get(Environment.apiContextPath('/api/system/org/listAllOrgsWithSelectedStatusByUser?userId=') + row.id).then((response) => {
orgTreeGridRef.value.setNodes(response.data);
});
}
},
};
const roleConfigure = {
queryFormColsNumber: 4,
queryFormColsAuto: false,
queryFormFields: [],
hideBottom: true,
tableInitLoadData: false,
tableLeftColumnStickyNumber: 0,
tableTitle: t('system.role.gridTitle'),
tableRowKey: 'id',
tableDataUrl: '',
tablePagination: {
sortBy: 'lastModifyDate',
descending: true,
reqPageStart: 0,
rowsPerPage: 0,
},
toolbar: {
buttons: [
'refresh',
{
name: 'addRole',
label: t('system.role.action.addRole'),
icon: '',
enable: () => {
if (userGridRef.value) {
return userGridRef.value.getSelectedRows().length > 0;
}
return false;
},
click: () => {
selectRoleDialog.value.show({ userGrid: userGridRef, roleGrid: roleGridRef });
},
},
{
name: 'addAllRole',
label: t('system.role.action.addAllRole'),
icon: '',
enable: () => {
if (userGridRef.value) {
console.log(userGridRef.value.getSelectedRows());
return userGridRef.value.getSelectedRows().length > 0;
}
return false;
},
click: () => {
axios
.post(Environment.apiContextPath('/api/system/user/addAllRoles'), {
one: userGridRef.value.getSelectedRows()[0].id,
many: [],
})
.then((response) => {
axios
.get(Environment.apiContextPath('/api/system/role/queryRolesByUser?userId=') + userGridRef.value.getSelectedRows()[0].id)
.then((response) => {
roleGridRef.value.replaceRowsFun(response.data.content);
});
});
},
},
{
name: 'removeRole',
label: t('system.role.action.removeRole'),
icon: '',
enable: () => {
if (userGridRef.value) {
return userGridRef.value.getSelectedRows().length > 0;
}
return false;
},
click: () => {
const roleIds = [];
for (const role of roleGridRef.value.getSelectedRows()) {
roleIds.push(role.id);
}
axios
.post(Environment.apiContextPath('/api/system/user/removeRoles'), {
one: userGridRef.value.getSelectedRows()[0].id,
many: roleIds,
})
.then((response) => {
axios
.get(Environment.apiContextPath('/api/system/role/queryRolesByUser?userId=') + userGridRef.value.getSelectedRows()[0].id)
.then((response) => {
roleGridRef.value.replaceRowsFun(response.data.content);
});
});
},
},
{
name: 'removeAllRole',
label: t('system.role.action.removeAllRole'),
icon: '',
enable: () => {
if (userGridRef.value) {
return userGridRef.value.getSelectedRows().length > 0;
}
return false;
},
click: () => {
axios
.post(Environment.apiContextPath('/api/system/user/removeAllRoles'), {
one: userGridRef.value.getSelectedRows()[0].id,
many: [],
})
.then((response) => {
axios
.get(Environment.apiContextPath('/api/system/role/queryRolesByUser?userId=') + userGridRef.value.getSelectedRows()[0].id)
.then((response) => {
roleGridRef.value.replaceRowsFun(response.data.content);
});
});
},
},
],
},
tableColumns: [
{ name: 'code', label: t('code') },
{ name: 'name', label: t('name') },
{ name: 'enable', label: t('isEnable'), format: (value) => (value ? t('yes') : t('no')) },
],
};
const orgConfigure = {
actions: [
{
name: 'save',
label: '保存',
click: () => {
axios
.post(Environment.apiContextPath('/api/system/user/updateOrgs'), {
one: userGridRef.value.getSelectedRows()[0].id,
many: orgTreeGridRef.value.getTicked(),
})
.then((response) => {});
},
},
],
};
</script>

15
io.sc.platform.core.frontend/src/views/likm/InfoPanel.vue

@ -0,0 +1,15 @@
<template>
<div>
<w-info-panel :column-num="2" label-align="center" value-align="center" :info="infoArray" dense> </w-info-panel>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
const infoArray = [
{ label: '姓名', value: '张三' },
{ label: '年龄', value: 18 },
{ label: '性别', value: '男' },
{ label: '爱好', value: '唱跳' },
];
</script>

154
io.sc.platform.core.frontend/src/views/likm/QuasarGrid.vue

@ -0,0 +1,154 @@
<template>
<div>
<q-table
title="Treats"
table-style="height: 500px;"
:rows="rows"
:columns="columns"
row-key="name"
:flat="true"
:selection="'single'"
:separator="'cell'"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const columns = [
{
name: 'name',
required: true,
label: 'Dessert (100g serving)',
align: 'left',
field: (row) => row.name,
format: (val) => `${val}`,
sortable: true,
},
{ name: 'calories', align: 'center', label: 'Calories', field: 'calories', sortable: true },
{ name: 'fat', label: 'Fat (g)', field: 'fat', sortable: true },
{ name: 'carbs', label: 'Carbs (g)', field: 'carbs' },
{ name: 'protein', label: 'Protein (g)', field: 'protein' },
{ name: 'sodium', label: 'Sodium (mg)', field: 'sodium' },
{ name: 'calcium', label: 'Calcium (%)', field: 'calcium', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) },
{ name: 'iron', label: 'Iron (%)', field: 'iron', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) },
];
const rows = [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: '14%',
iron: '1%',
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: '8%',
iron: '1%',
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
sodium: 337,
calcium: '6%',
iron: '7%',
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: '3%',
iron: '8%',
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: '7%',
iron: '16%',
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: '0%',
iron: '0%',
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
sodium: 38,
calcium: '0%',
iron: '2%',
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: '0%',
iron: '45%',
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: '2%',
iron: '22%',
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
sodium: 54,
calcium: '12%',
iron: '6%',
},
];
const separator = ref('vertical');
</script>
<style scoped lang="css">
:deep(.q-table) {
border-collapse: collapse;
}
.q-table--vertical-separator td,
.q-table--vertical-separator th,
.q-table--cell-separator td,
.q-table--cell-separator th {
border-bottom-width: 1px !important;
}
</style>

153
io.sc.platform.core.frontend/src/views/likm/Toolbar.vue

@ -0,0 +1,153 @@
<template>
<div>
<div class="flex flex-nowrap">
<div class="flex-none">这是测试标题</div>
<div class="flex-1">
<w-toolbar :no-icon="toolbar.noIcon" :align="toolbar.align" :x-gap="toolbar.xGap" :buttons="toolbar.buttons"></w-toolbar>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const toolbar = {
xGap: 8,
noIcon: false,
align: 'right',
buttons: [
[
{
name: 'query',
icon: 'search',
label: '查询',
beforeClick: (tableRef, context) => {},
click: (tableRef, context, _click) => {
console.info('查询');
},
afterClick: (tableRef, context) => {},
},
'separator',
{
name: 'moreQuery',
icon: 'search',
label: '更多查询',
click: (tableRef, context, _click) => {
console.info('更多查询');
},
},
'separator',
{
name: 'reset',
icon: 'autorenew',
label: '重置',
click: (tableRef, context, _click) => {
console.info('重置');
},
},
],
'separator',
{
name: 'addnew',
extend: 'add',
icon: 'add',
label: '新增',
beforeClick: (tableRef, context) => {},
click: (tableRef, context, _click) => {
console.info('新增');
},
afterClick: (tableRef, context) => {},
},
'separator',
[
{
name: 'edit',
icon: 'edit',
label: '编辑',
click: (tableRef, context, _click) => {
console.info('编辑');
},
},
{
name: 'clone',
icon: 'edit',
label: '复制',
click: (tableRef, context, _click) => {
console.info('复制');
},
},
'separator',
[
{
name: 'editConfig',
icon: 'edit',
label: '修改配置信息',
click: (tableRef, context, _click) => {
console.info('修改配置信息');
},
},
{
name: 'editBasicInfo',
icon: 'edit',
label: '修改基本信息',
click: (tableRef, context, _click) => {
console.info('修改基本信息');
},
},
{
name: 'editVersionInfo',
icon: 'edit',
label: '修改版本信息',
click: (tableRef, context, _click) => {
console.info('修改版本信息');
},
},
'separator',
[
{
name: 'edit1',
icon: 'edit',
label: '修改详细信息1',
click: (tableRef, context, _click) => {
console.info('修改详细信息1');
},
},
{
name: 'edit2',
icon: 'edit',
label: '修改详细信息2',
click: (tableRef, context, _click) => {
console.info('修改详细信息2');
},
},
],
],
],
'separator',
{
name: 'remove',
icon: 'remove',
label: '删除',
click: (tableRef, context, _click) => {
console.info('删除');
},
},
// { icon: 'home', label: '1' },
// { separator: true },
// { icon: 'home', label: '2' },
// { icon: 'home', label: '3' },
// { icon: 'home', label: '4' },
// { separator: true },
// { separator: true },
// { icon: 'home', label: '5' },
// { icon: 'home', label: '6' },
// { icon: 'home', label: '7' },
// { icon: 'home', label: '8' },
// { icon: 'home', label: '9' },
// { icon: 'home', label: '10' },
// { icon: 'home', label: '11' },
// { icon: 'home', label: '12' },
// { icon: 'home', label: '13' },
],
};
</script>

30
io.sc.platform.core.frontend/template-project/src/views/Form1.vue

@ -0,0 +1,30 @@
<template>
<div>
<w-form :fields="fields" :cols-num-auto="false" :cols-num="3"></w-form>
</div>
</template>
<script setup lang="ts">
const fields = [
{
label: '姓名',
name: 'name',
type: 'text',
required: true,
colspan: 2,
button: {
round: false,
icon: 'home',
label: '选择',
click: () => {
console.info('11111');
},
},
},
{
label: '年龄',
name: 'age',
type: 'number',
required: true,
},
];
</script>

4
io.sc.platform.core.frontend/template-project/src/views/Grid1.vue

@ -0,0 +1,4 @@
<template>
<div>2222</div>
</template>
<script setup lang="ts"></script>
Loading…
Cancel
Save