..
This commit is contained in:
@@ -102,143 +102,6 @@
|
||||
background-color: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
|
||||
.ele-setting-wrapper {
|
||||
padding: 20px 18px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-title {
|
||||
font-size: 13px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div {
|
||||
width: 52px;
|
||||
height: 36px;
|
||||
line-height: 1;
|
||||
font-size: 18px;
|
||||
border-radius: 3px;
|
||||
margin: 0 20px 30px 0;
|
||||
padding: 14px 0 0 24px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
-webkit-transition: background-color 0.2s;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div:after,
|
||||
.ele-setting-wrapper .ele-setting-theme > div:before,
|
||||
.ele-setting-wrapper .ele-setting-theme > div > .ele-bg-primary {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
-webkit-transition: background-color 0.2s;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div:after {
|
||||
width: 14px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-head-dark:before,
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-layout-mix:after,
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-layout-mix:before,
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-side-dark:after {
|
||||
background: #001529;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-head-dark:before,
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-head-light:before,
|
||||
.ele-setting-wrapper .ele-setting-theme > div > .ele-bg-primary {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-layout-top {
|
||||
padding-left: 19px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-theme > div.ele-layout-top:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-colors {
|
||||
color: #fff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-colors .ele-setting-color-item {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 22px;
|
||||
border-radius: 2px;
|
||||
margin: 8px 8px 0 0;
|
||||
display: inline-block;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-item {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-item .setting-item-title {
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .ele-setting-item .setting-item-control {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .el-divider {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ele-setting-wrapper .el-alert + .ele-setting-button-group {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.ele-setting-dark .ele-setting-theme > div {
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.ele-setting-dark .ele-setting-theme > div:after,
|
||||
.ele-setting-dark .ele-setting-theme > div:before,
|
||||
.ele-setting-dark .ele-setting-theme > div > .ele-bg-primary {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
|
||||
.ele-setting-dark .ele-setting-theme > div.ele-head-dark:before,
|
||||
.ele-setting-dark .ele-setting-theme > div.ele-layout-mix:after,
|
||||
.ele-setting-dark .ele-setting-theme > div.ele-layout-mix:before,
|
||||
.ele-setting-dark .ele-setting-theme > div.ele-side-dark:after {
|
||||
background: #262626;
|
||||
}
|
||||
|
||||
.ele-setting-color-picker.el-color-picker {
|
||||
height: auto;
|
||||
margin-top: 8px;
|
||||
@@ -318,66 +181,6 @@
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.ele-admin-collapse .ele-admin-sidebar-menus > .el-scrollbar > .el-scrollbar__wrap > .el-scrollbar__view > .el-menu > .el-menu-item .ele-menu-badge,
|
||||
.ele-admin-collapse
|
||||
.ele-admin-sidebar-menus
|
||||
> .el-scrollbar
|
||||
> .el-scrollbar__wrap
|
||||
> .el-scrollbar__view
|
||||
> .el-menu
|
||||
> .el-submenu
|
||||
> .el-submenu__title
|
||||
.ele-menu-badge {
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ele-admin-header-nav > .el-scrollbar > .el-scrollbar__wrap > .el-scrollbar__view > .el-menu > .el-menu-item .ele-menu-badge,
|
||||
.ele-admin-header-nav > .el-scrollbar > .el-scrollbar__wrap > .el-scrollbar__view > .el-menu > .el-submenu > .el-submenu__title .ele-menu-badge {
|
||||
position: static;
|
||||
right: auto;
|
||||
top: auto;
|
||||
display: inline-block;
|
||||
vertical-align: 6px;
|
||||
margin: 0 0 0 2px;
|
||||
}
|
||||
|
||||
.ele-admin-sidebar-nav-menu > .el-scrollbar > .el-scrollbar__wrap > .el-scrollbar__view > .el-menu > .el-menu-item .ele-menu-badge,
|
||||
.ele-admin-sidebar-nav-menu
|
||||
> .el-scrollbar
|
||||
> .el-scrollbar__wrap
|
||||
> .el-scrollbar__view
|
||||
> .el-menu
|
||||
> .el-submenu
|
||||
> .el-submenu__title
|
||||
.ele-menu-badge {
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ele-admin-nav-collapse
|
||||
.ele-admin-sidebar-nav-menu
|
||||
> .el-scrollbar
|
||||
> .el-scrollbar__wrap
|
||||
> .el-scrollbar__view
|
||||
> .el-menu
|
||||
> .el-menu-item
|
||||
.ele-menu-badge,
|
||||
.ele-admin-nav-collapse
|
||||
.ele-admin-sidebar-nav-menu
|
||||
> .el-scrollbar
|
||||
> .el-scrollbar__wrap
|
||||
> .el-scrollbar__view
|
||||
> .el-menu
|
||||
> .el-submenu
|
||||
> .el-submenu__title
|
||||
.ele-menu-badge {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.hidden-xs-only {
|
||||
display: none !important;
|
||||
@@ -24318,18 +24121,6 @@ s:not([class]) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ele-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.ele-body.ele-body-card {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ele-body.ele-body-card .el-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ele-bottom-tool {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@@ -648,46 +648,76 @@ td.no-b {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.form-box{
|
||||
.form-box {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.descriptions-form .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
/**********start****************设置工作流表单el-description嵌套el-form的格式 必须要使用flow-task-form!!!***********************start*****************/
|
||||
|
||||
.el-descriptions__table {
|
||||
.flow-task-form .el-descriptions__table {
|
||||
table-layout: fixed !important;
|
||||
}
|
||||
|
||||
.el-descriptions-item__label {
|
||||
text-align: center !important;
|
||||
width: 14% !important;
|
||||
}
|
||||
|
||||
.descriptions-form th.el-descriptions-item__label:has(+ td.el-descriptions-item__content > div.el-form-item.is-required):before {
|
||||
content: "*";
|
||||
color: #f56c6c;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.descriptions-form .el-form-item .el-form-item__label {
|
||||
.flow-task-form .el-form-item .el-form-item__label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.descriptions-form .el-form-item .el-form-item__content > div {
|
||||
width: 100%;
|
||||
.flow-task-form .el-descriptions-item__label.is-bordered-label {
|
||||
color: rgb(102, 102, 102) !important;
|
||||
font-weight: bold !important;
|
||||
background: rgb(248, 249, 250) !important;
|
||||
padding: 8px 12px !important;
|
||||
border: 1px solid rgb(221, 221, 221) !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.flow-task-form .el-descriptions .is-bordered .el-descriptions-item__cell {
|
||||
border: 1px solid rgb(221, 221, 221) !important;
|
||||
}
|
||||
|
||||
.flow-task-form .el-descriptions-item__content {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
.descriptions-form .el-form-item input, .descriptions-form .el-form-item textarea {
|
||||
.flow-task-form .el-form-item .el-form-item__content > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.flow-task-form .el-descriptions-item__cell.el-descriptions-item__content .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/**********start****************设置工作流表单el-description嵌套el-form的格式 必填项加星号***********************start*****************/
|
||||
.flow-task-form th.el-descriptions-item__label:has(+ td.el-descriptions-item__content > div.el-form-item.is-required):before {
|
||||
content: "*";
|
||||
color: #ff0202;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
.flow-task-form .el-form-item input, .flow-task-form .el-form-item textarea, .flow-task-form .el-form-item .el-radio.is-bordered {
|
||||
/*border: none;*/
|
||||
border-radius: unset;
|
||||
border: 1px solid rgb(221, 221, 221);
|
||||
}
|
||||
|
||||
.descriptions-form .el-form-item .el-date-editor {
|
||||
.flow-task-form .el-form-item .el-radio.is-bordered {
|
||||
border: 1px solid rgb(221, 221, 221);
|
||||
}
|
||||
|
||||
.flow-task-form .el-form-item .el-radio.is-bordered.is-checked {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
|
||||
.flow-task-form .el-form-item .el-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/*********END********设置工作流表单el-description嵌套el-form的格式**************************END*********/
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
:root {
|
||||
--color-primary: #006db9; /* 主色 */
|
||||
--color-primary-1: #e6f3ff; /* 最浅,用于浅色背景 */
|
||||
--color-primary-2: #b3d9ff; /* 较浅,用于 hover/light-bg */
|
||||
--color-primary-3: #80c0ff; /* 轻微强调 */
|
||||
--color-primary-4: #4da6ff; /* 中等强调 */
|
||||
--color-primary-5: #1a8cff; /* 较强强调 */
|
||||
--color-primary-6: #006db9; /* 主色(默认) */
|
||||
--color-primary-7: #005aa3; /* 点击/active 状态 */
|
||||
--color-primary-8: #00468c; /* 较深,用于文字/强调 */
|
||||
--color-primary-9: #003370; /* 更深,用于深色模式 */
|
||||
--color-primary-10: #002054; /* 最深,用于特殊强调 */
|
||||
|
||||
--color-success: #52c41a;
|
||||
--color-success-1: #f6ffed;
|
||||
--color-success-2: #d9f7be;
|
||||
--color-success-3: #b7eb8f;
|
||||
--color-success-4: #95de64;
|
||||
--color-success-5: #73d13d;
|
||||
--color-success-6: #52c41a;
|
||||
--color-success-7: #389e0d;
|
||||
--color-success-8: #237804;
|
||||
--color-success-9: #135200;
|
||||
--color-success-10: #092b00;
|
||||
--color-warning: #faad14;
|
||||
--color-warning-1: #fffbe6;
|
||||
--color-warning-2: #fff1b8;
|
||||
--color-warning-3: #ffe58f;
|
||||
--color-warning-4: #ffd666;
|
||||
--color-warning-5: #ffc53d;
|
||||
--color-warning-6: #faad14;
|
||||
--color-warning-7: #d48806;
|
||||
--color-warning-8: #ad6800;
|
||||
--color-warning-9: #874d00;
|
||||
--color-warning-10: #613400;
|
||||
--color-danger: #ff4d4f;
|
||||
--color-danger-1: #fff1f0;
|
||||
--color-danger-2: #ffccc7;
|
||||
--color-danger-3: #ffa39e;
|
||||
--color-danger-4: #ff7875;
|
||||
--color-danger-5: #ff4d4f;
|
||||
--color-danger-6: #f5222d;
|
||||
--color-danger-7: #cf1322;
|
||||
--color-danger-8: #a8071a;
|
||||
--color-danger-9: #820014;
|
||||
--color-danger-10: #5c0011;
|
||||
--color-info: #909399;
|
||||
--color-info-1: #f4f4f5;
|
||||
--color-info-2: #dedfe0;
|
||||
--color-info-3: #d3d4d6;
|
||||
--color-info-4: #bcbec2;
|
||||
--color-info-5: #a6a9ad;
|
||||
--color-info-6: #909399;
|
||||
--color-info-7: #6e7074;
|
||||
--color-info-8: #58595c;
|
||||
--color-info-9: #414244;
|
||||
--color-info-10: #2a2b2b;
|
||||
--color-white: #fff;
|
||||
--color-white-5: hsla(0, 0%, 100%, 0.5);
|
||||
--color-black: #000;
|
||||
--color-black-1: rgba(0, 0, 0, 0.1);
|
||||
--color-black-3: rgba(0, 0, 0, 0.3);
|
||||
--color-black-5: rgba(0, 0, 0, 0.5);
|
||||
--color-text-primary: #303133;
|
||||
--color-text-regular: #606266;
|
||||
--color-text-secondary: #909399;
|
||||
--color-text-placeholder: #c0c4cc;
|
||||
--color-text-dark-primary: #fff;
|
||||
--color-text-dark-regular: hsla(0, 0%, 100%, 0.8);
|
||||
--border-color-base: #ddd;
|
||||
--border-color-light: #e5e5e5;
|
||||
--border-color-lighter: #ededed;
|
||||
--border-color-extra-light: #f4f4f4;
|
||||
--background-color-base: #fafafa;
|
||||
--box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12);
|
||||
--box-shadow-light: 0 4px 12px 0 rgba(0, 0, 0, 0.15);
|
||||
--checkbox-background-color: var(--color-white);
|
||||
--radio-input-background-color: var(--color-white);
|
||||
--radio-checked-icon-color: var(--color-white);
|
||||
--input-background-color: var(--color-white);
|
||||
--button-default-background-color: var(--color-white);
|
||||
--button-disabled-background-color: var(--color-white);
|
||||
--button-default-border-color-5: hsla(0, 0%, 87%, 0.5);
|
||||
--table-header-background-color: var(--background-color-base);
|
||||
--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, 0.12);
|
||||
--popup-modal-background-color: #000;
|
||||
--popup-modal-opacity: 0.5;
|
||||
--popover-background-color: var(--color-white);
|
||||
--dialog-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
--tooltip-fill: var(--color-text-primary);
|
||||
--tooltip-color: var(--color-text-dark-primary);
|
||||
--tooltip-border-color: var(--tooltip-fill);
|
||||
--scrollbar-background-color: hsla(0, 0%, 67%, 0.5);
|
||||
--scrollbar-hover-background-color: hsla(0, 0%, 67%, 0.8);
|
||||
--backtop-background-color: var(--color-white);
|
||||
--backtop-font-color: var(--color-primary);
|
||||
--backtop-hover-background-color: var(--border-color-extra-light);
|
||||
--avatar-background-color: #c0c4cc;
|
||||
--skeleton-color: #f2f2f2;
|
||||
--skeleton-to-color: #e6e6e6;
|
||||
--svg-monochrome-grey: #dcdde0;
|
||||
--loading-mask-bg: hsla(0, 0%, 100%, 0.9);
|
||||
--menu-dark-background: #191a23;
|
||||
--layout-body-background: #f0f2f5;
|
||||
--sidebar-background-light: var(--color-white);
|
||||
--sidebar-background-dark: var(--menu-dark-background);
|
||||
--sidebar-light-shadow: 1px 3px 3px rgba(0, 21, 41, 0.08);
|
||||
--sidebar-dark-shadow: 0 4px 4px rgba(0, 0, 0, 0.35);
|
||||
--header-background: var(--menu-dark-background);
|
||||
--header-light-background: var(--color-white);
|
||||
--header-light-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
--header-dark-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
--header-tool-hover-bg: rgba(0, 0, 0, 0.025);
|
||||
--header-dark-tool-hover-bg: hsla(0, 0%, 100%, 0.05);
|
||||
--logo-light-shadow: 1px 2px 3px rgba(0, 21, 41, 0.08);
|
||||
--logo-dark-shadow: 0 3px 4px rgba(0, 0, 0, 0.35);
|
||||
--content-image-filter: none;
|
||||
--el-mask-color: rgba(255, 255, 255, 0.4);
|
||||
--el-mask-color-extra-light: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -38,32 +38,32 @@ $.ajaxSetup({
|
||||
})
|
||||
|
||||
//初始化pjax
|
||||
;(function initPjax() {
|
||||
if ($.support.pjax) {
|
||||
$(document).pjax("a[data-pjax]", "#container", {
|
||||
maxCacheLength: 0,
|
||||
push: false,
|
||||
replace: true,
|
||||
fragment: "#container",
|
||||
timeout: 8000
|
||||
})
|
||||
$(document).on("pjax:send", function () {
|
||||
NProgress.start()
|
||||
$(".ele-admin-content-view").hide()
|
||||
window.customStyleList = document.querySelectorAll('style[id^="style-"]')
|
||||
})
|
||||
|
||||
$(document).on("pjax:complete", function () {
|
||||
if (window.customStyleList) {
|
||||
window.customStyleList.forEach((style) => {
|
||||
style.parentNode.removeChild(style)
|
||||
})
|
||||
}
|
||||
NProgress.done()
|
||||
$(".ele-admin-content-view").show()
|
||||
})
|
||||
}
|
||||
})()
|
||||
// ;(function initPjax() {
|
||||
// if ($.support.pjax) {
|
||||
// $(document).pjax("a[data-pjax]", "#container", {
|
||||
// maxCacheLength: 0,
|
||||
// push: false,
|
||||
// replace: true,
|
||||
// fragment: "#container",
|
||||
// timeout: 8000
|
||||
// })
|
||||
// $(document).on("pjax:send", function () {
|
||||
// NProgress.start()
|
||||
// $(".ele-admin-content-view").hide()
|
||||
// window.customStyleList = document.querySelectorAll('style[id^="style-"]')
|
||||
// })
|
||||
//
|
||||
// $(document).on("pjax:complete", function () {
|
||||
// if (window.customStyleList) {
|
||||
// window.customStyleList.forEach((style) => {
|
||||
// style.parentNode.removeChild(style)
|
||||
// })
|
||||
// }
|
||||
// NProgress.done()
|
||||
// $(".ele-admin-content-view").show()
|
||||
// })
|
||||
// }
|
||||
// })()
|
||||
|
||||
function pjaxReplace(url, data = {}) {
|
||||
const { pathname, search } = location
|
||||
|
||||
@@ -17,7 +17,7 @@ const commonUtil = {
|
||||
if (response.config.responseType === "blob") {
|
||||
return response
|
||||
}
|
||||
if (response.data && response.data.code !== 0) {
|
||||
if (response.data && response.data.code !== 0 && response.data.code !== 200) {
|
||||
if (isMobile) {
|
||||
vant.Toast(response.data.msg)
|
||||
} else {
|
||||
@@ -25,7 +25,7 @@ const commonUtil = {
|
||||
}
|
||||
return Promise.reject(response.data)
|
||||
}
|
||||
return response.data
|
||||
return response.data || response.result
|
||||
},
|
||||
(error) => {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera
|
||||
@@ -217,6 +217,7 @@ const commonUtil = {
|
||||
function doSm2Encrypt(msgString) {
|
||||
return sm2.doEncrypt(msgString, publicKey, cipherMode)
|
||||
}
|
||||
|
||||
// SM2数组加密
|
||||
function doSm2ArrayEncrypt(msgString) {
|
||||
return sm2.doEncrypt(msgString, publicKey, cipherMode)
|
||||
|
||||
+7
-7
@@ -1450,7 +1450,7 @@
|
||||
.el-dropdown-menu__item:focus,
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
background-color: #ecf5ff;
|
||||
color: #66b1ff;
|
||||
color: var(--color-primary-6);
|
||||
}
|
||||
.el-dropdown-menu__item i {
|
||||
margin-right: 5px;
|
||||
@@ -2768,7 +2768,7 @@
|
||||
}
|
||||
.el-table-filter__list-item:hover {
|
||||
background-color: #ecf5ff;
|
||||
color: #66b1ff;
|
||||
color: var(--color-primary-6);
|
||||
}
|
||||
.el-table-filter__list-item.is-active {
|
||||
background-color: var(--color-primary);
|
||||
@@ -9789,7 +9789,7 @@
|
||||
}
|
||||
.el-tag--dark .el-tag__close:hover {
|
||||
color: #fff;
|
||||
background-color: #66b1ff;
|
||||
background-color: var(--color-primary-6);
|
||||
}
|
||||
.el-tag--dark.el-tag--info {
|
||||
background-color: #909399;
|
||||
@@ -10963,8 +10963,8 @@
|
||||
}
|
||||
.el-button--primary:focus,
|
||||
.el-button--primary:hover {
|
||||
background: #66b1ff;
|
||||
border-color: #66b1ff;
|
||||
background: var(--color-primary-6);
|
||||
border-color: var(--color-primary-6);
|
||||
color: #fff;
|
||||
}
|
||||
.el-button--primary.is-active,
|
||||
@@ -11266,7 +11266,7 @@
|
||||
}
|
||||
.el-button--text:focus,
|
||||
.el-button--text:hover {
|
||||
color: #66b1ff;
|
||||
color: var(--color-primary-6);
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -11785,7 +11785,7 @@
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.el-link.el-link--primary:hover {
|
||||
color: #66b1ff;
|
||||
color: var(--color-primary-6);
|
||||
}
|
||||
.el-link.el-link--primary.is-disabled {
|
||||
color: #a0cfff;
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
@charset "UTF-8";
|
||||
/* 数据量大与遮罩交互冲突优化 */
|
||||
.v-modal.v-modal-leave {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 6px;
|
||||
border: 2px solid transparent;
|
||||
background-color: var(--scrollbar-background-color);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 15px 20px 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border-color-lighter);
|
||||
}
|
||||
|
||||
.el-dialog__title {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn{
|
||||
top: auto;
|
||||
right: auto;
|
||||
line-height: 1;
|
||||
font-size: 18px;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding-bottom: 12px;
|
||||
border-top: 1px solid var(--border-color-lighter);
|
||||
}
|
||||
|
||||
.el-table thead {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-table td.el-table__cell div {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
|
||||
.el-card {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-card + .el-card {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-message:not(.ele-message-border){
|
||||
background: var(--popover-background-color);
|
||||
-webkit-box-shadow: var(--box-shadow-light);
|
||||
box-shadow: var(--box-shadow-light);
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
min-width: auto;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.el-message .el-message__icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.el-message .el-icon-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.el-message:not(.ele-message-border) .el-message__content {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.el-message .el-message__content {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.el-button{
|
||||
border-radius: 2px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13308
File diff suppressed because one or more lines are too long
@@ -0,0 +1,648 @@
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "formButton",
|
||||
props: {
|
||||
task_id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 是否显示审核意见框
|
||||
show_comment: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义按钮配置
|
||||
custom_buttons: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 是否显示流程图
|
||||
show_process_diagram: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示办理记录
|
||||
show_process_records: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 办理记录展示模式:timeline, table
|
||||
record_display_mode: {
|
||||
type: String,
|
||||
default: "timeline",
|
||||
validator: (value) => {
|
||||
return ["timeline", "table"].includes(value)
|
||||
}
|
||||
},
|
||||
// 业务ID,用于获取流程记录
|
||||
business_id: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
comment: "", // 审核意见
|
||||
nextNodeInfo: null, // 下一节点信息
|
||||
currentTaskName: "部门审核", // 当前办理节点名称
|
||||
availableActions: [], // 可用操作
|
||||
sequenceFlows: [], // 目标节点
|
||||
selectedAction: "", // 选中的操作类型
|
||||
rejectTaskOptions: [], // 驳回到指定节点的选项
|
||||
selectedRejectTask: "", // 选中的驳回节点
|
||||
assigneeOptions: [], // 执行人选项
|
||||
selectedAssignee: "", // 选中的执行人
|
||||
|
||||
// 控制显示
|
||||
showProcessDiagram: true, // 是否显示流程图
|
||||
showProcessRecords: true, // 是否显示办理记录
|
||||
recordDisplayMode: 'timeline', // 办理记录展示模式:timeline, table
|
||||
|
||||
// 模拟数据 - 办理记录
|
||||
mockProcessRecords: [
|
||||
{
|
||||
nodeName: "发起申请",
|
||||
userName: "张三",
|
||||
time: "2023-05-10 09:30:00",
|
||||
action: "提交申请",
|
||||
comment: "请审批",
|
||||
type: "primary"
|
||||
},
|
||||
{
|
||||
nodeName: "部门审核",
|
||||
userName: "李四",
|
||||
time: "2023-05-10 11:20:00",
|
||||
action: "处理中",
|
||||
comment: "",
|
||||
type: "warning"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.task_id) {
|
||||
this.getNextNodeInfo()
|
||||
}
|
||||
|
||||
// 初始化显示状态
|
||||
this.showProcessDiagram = this.show_process_diagram
|
||||
this.showProcessRecords = this.show_process_records
|
||||
this.recordDisplayMode = this.record_display_mode
|
||||
|
||||
// 模拟获取流程记录
|
||||
this.mockGetProcessRecords()
|
||||
},
|
||||
|
||||
watch: {
|
||||
// 监听props变化
|
||||
show_process_diagram(val) {
|
||||
this.showProcessDiagram = val
|
||||
},
|
||||
show_process_records(val) {
|
||||
this.showProcessRecords = val
|
||||
},
|
||||
record_display_mode(val) {
|
||||
this.recordDisplayMode = val
|
||||
},
|
||||
business_id: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
// 清空现有记录
|
||||
this.mockProcessRecords = []
|
||||
// 重新获取流程记录
|
||||
this.mockGetProcessRecords()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取下一节点信息
|
||||
getNextNodeInfo() {
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.post("/easy-flowable/task/nextNodeVariables/" + this.task_id)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.nextNodeInfo = res.result
|
||||
// 获取当前办理节点名称
|
||||
if (res.result.task && res.result.task.name) {
|
||||
this.currentTaskName = res.result.task.name
|
||||
}
|
||||
// 处理可用操作
|
||||
if (res.result.attributes && res.result.attributes.actions) {
|
||||
this.availableActions = res.result.attributes.actions
|
||||
}
|
||||
// 处理目标节点
|
||||
if (res.result.sequenceFlow) {
|
||||
this.sequenceFlows = res.result.sequenceFlow
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg || "获取下一节点信息失败")
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("获取下一节点信息失败", error)
|
||||
this.$message.error("获取下一节点信息失败")
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 获取可驳回的节点列表
|
||||
getRejectTaskList() {
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.get("/easy-flowable/task/flowBackNodes/" + this.task_id)
|
||||
.then((res) => {
|
||||
if (res.code === 0 && res.result) {
|
||||
this.rejectTaskOptions = res.result.map((item) => ({
|
||||
label: item.nodeName,
|
||||
value: item.nodeId
|
||||
}))
|
||||
if (this.rejectTaskOptions.length > 0) {
|
||||
this.selectedRejectTask = this.rejectTaskOptions[0].value
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg || "获取可驳回节点失败")
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("获取可驳回节点失败", error)
|
||||
this.$message.error("获取可驳回节点失败")
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 处理按钮点击
|
||||
handleAction(action) {
|
||||
this.selectedAction = action
|
||||
|
||||
// 如果是驳回到指定节点,需要获取可驳回的节点列表
|
||||
if (action === "REJECT_TO_TASK") {
|
||||
this.getRejectTaskList()
|
||||
return
|
||||
}
|
||||
|
||||
// 执行操作
|
||||
this.submitAction()
|
||||
},
|
||||
// 提交操作
|
||||
submitAction() {
|
||||
if (this.show_comment && !this.comment && ["AGREE", "REJECT", "REJECT_TO_TASK", "REBUT"].includes(this.selectedAction)) {
|
||||
this.$message.warning("请填写审核意见")
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
taskId: this.task_id,
|
||||
comment: this.comment,
|
||||
type: this.getActionCode(this.selectedAction)
|
||||
}
|
||||
|
||||
// 添加目标节点变量(自动选择第一个)
|
||||
if (this.sequenceFlows.length > 0 && ["AGREE"].includes(this.selectedAction)) {
|
||||
params.variables = {
|
||||
sequenceFlow: this.sequenceFlows[0].value
|
||||
}
|
||||
}
|
||||
|
||||
// 添加驳回节点信息
|
||||
if (this.selectedAction === "REJECT_TO_TASK" && this.selectedRejectTask) {
|
||||
params.targetTaskDefinitionKey = this.selectedRejectTask
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.post("/easy-flowable/task/complete", params)
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
this.$emit("success", {
|
||||
action: this.selectedAction,
|
||||
result: res.result
|
||||
})
|
||||
} else {
|
||||
this.$message.error(res.msg || "操作失败")
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("操作失败", error)
|
||||
this.$message.error("操作失败")
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 获取操作类型代码
|
||||
getActionCode(action) {
|
||||
const actionMap = {
|
||||
START: "0",
|
||||
AGREE: "1",
|
||||
REBUT: "2",
|
||||
REVOCATION: "3",
|
||||
REJECT: "4",
|
||||
REJECT_TO_TASK: "5",
|
||||
DELEGATE: "6",
|
||||
ASSIGN: "7",
|
||||
STOP: "8",
|
||||
BEFORE_SIGN: "9",
|
||||
AFTER_SIGN: "10",
|
||||
CANCELLATION: "11",
|
||||
ADD_COMMENT: "12",
|
||||
DEL_COMMENT: "13",
|
||||
RESUBMIT: "14"
|
||||
}
|
||||
return actionMap[action] || "1"
|
||||
},
|
||||
// 获取操作按钮文本
|
||||
getActionText(action) {
|
||||
const actionTextMap = {
|
||||
START: "启动流程",
|
||||
AGREE: "同意",
|
||||
REBUT: "拒绝",
|
||||
REVOCATION: "撤回",
|
||||
REJECT: "驳回",
|
||||
REJECT_TO_TASK: "驳回到指定节点",
|
||||
DELEGATE: "委派",
|
||||
ASSIGN: "转办",
|
||||
STOP: "终止",
|
||||
BEFORE_SIGN: "前加签",
|
||||
AFTER_SIGN: "后加签",
|
||||
CANCELLATION: "作废",
|
||||
ADD_COMMENT: "添加评论",
|
||||
DEL_COMMENT: "删除评论",
|
||||
RESUBMIT: "重新提交"
|
||||
}
|
||||
return actionTextMap[action] || action
|
||||
},
|
||||
// 获取操作按钮类型
|
||||
getActionType(action) {
|
||||
const typeMap = {
|
||||
AGREE: "primary",
|
||||
REBUT: "danger",
|
||||
REJECT: "warning",
|
||||
REJECT_TO_TASK: "warning"
|
||||
}
|
||||
return typeMap[action] || "default"
|
||||
},
|
||||
|
||||
// 模拟获取流程记录
|
||||
mockGetProcessRecords() {
|
||||
// 这里可以根据实际需求调整模拟数据
|
||||
// 实际项目中,可以通过API获取真实数据
|
||||
// 例如:this.$axios.post("/platform/wf/processCommon/approvalRecord", { businessNo: this.business_id })
|
||||
|
||||
// 模拟异步获取数据
|
||||
setTimeout(() => {
|
||||
// 如果已经有模拟数据,则不再添加
|
||||
if (this.mockProcessRecords.length <= 2) {
|
||||
// 添加更多模拟数据
|
||||
this.mockProcessRecords.push(
|
||||
{
|
||||
nodeName: "主管审核",
|
||||
userName: "王五",
|
||||
time: "2023-05-09 14:30:00",
|
||||
action: "同意",
|
||||
comment: "同意申请,请总经理审批",
|
||||
type: "success"
|
||||
},
|
||||
{
|
||||
nodeName: "发起申请",
|
||||
userName: "张三",
|
||||
time: "2023-05-09 10:15:00",
|
||||
action: "提交申请",
|
||||
comment: "请审批我的申请",
|
||||
type: "info"
|
||||
}
|
||||
)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-button-container">
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 表单信息插槽 -->
|
||||
<div class="form-info-container">
|
||||
<slot name="form-info"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 流程图展示 -->
|
||||
<div class="process-diagram-container" v-if="showProcessDiagram">
|
||||
<div class="section-title">流程图</div>
|
||||
<div class="process-diagram">
|
||||
<!-- 模拟流程图 -->
|
||||
<div class="mock-process-diagram">
|
||||
<div class="mock-node start">开始</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node" :class="{ active: currentTaskName === '部门审核' }">部门审核</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node">主管审核</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node">总经理审核</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node end">结束</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 办理记录 -->
|
||||
<div class="process-records-container" v-if="showProcessRecords">
|
||||
<div class="section-title">
|
||||
<span>办理记录</span>
|
||||
<div class="display-mode-selector">
|
||||
<el-radio-group v-model="recordDisplayMode" size="small">
|
||||
<el-radio-button label="timeline">时间线</el-radio-button>
|
||||
<el-radio-button label="table">表格</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-records">
|
||||
<!-- 时间线模式 -->
|
||||
<el-timeline v-if="recordDisplayMode === 'timeline'">
|
||||
<el-timeline-item v-for="(record, index) in mockProcessRecords" :key="index" :timestamp="record.time" :type="record.type">
|
||||
<div class="record-content">
|
||||
<div class="record-title">{{ record.nodeName }} - {{ record.userName }}</div>
|
||||
<div class="record-action">{{ record.action }}</div>
|
||||
<div class="record-comment" v-if="record.comment">{{ record.comment }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<!-- 表格模式 -->
|
||||
<el-table v-else-if="recordDisplayMode === 'table'" :data="mockProcessRecords" stripe style="width: 100%">
|
||||
<el-table-column prop="nodeName" label="节点名称" width="120"></el-table-column>
|
||||
<el-table-column prop="userName" label="操作人" width="100"></el-table-column>
|
||||
<el-table-column prop="time" label="操作时间" width="160"></el-table-column>
|
||||
<el-table-column prop="action" label="操作类型" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.type">{{ scope.row.action }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="comment" label="操作意见">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.comment">{{ scope.row.comment }}</span>
|
||||
<span v-else class="no-comment">无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部固定操作区域 -->
|
||||
<div class="footer-actions">
|
||||
<!-- 当前办理节点 -->
|
||||
<div v-if="currentTaskName" class="current-task-container">
|
||||
<div class="current-task-label">当前节点:</div>
|
||||
<div class="current-task-info">{{ currentTaskName }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 目标节点展示 -->
|
||||
<div v-if="sequenceFlows.length > 0" class="flow-container">
|
||||
<div class="flow-label">目标节点:</div>
|
||||
<div class="target-node-info">
|
||||
<span v-for="item in sequenceFlows" :key="item.value">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核意见 -->
|
||||
<div v-if="show_comment" class="comment-container">
|
||||
<div class="comment-label">审核意见:</div>
|
||||
<el-input v-model="comment" type="textarea" :rows="3" placeholder="请输入审核意见"></el-input>
|
||||
</div>
|
||||
|
||||
<!-- 驳回到指定节点选择 -->
|
||||
<div v-if="selectedAction === 'REJECT_TO_TASK' && rejectTaskOptions.length > 0" class="reject-container">
|
||||
<div class="reject-label">驳回到:</div>
|
||||
<el-select v-model="selectedRejectTask" placeholder="请选择驳回节点">
|
||||
<el-option v-for="item in rejectTaskOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="submitAction">确认</el-button>
|
||||
<el-button @click="selectedAction = ''">取消</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="button-container">
|
||||
<!-- 系统预设按钮 -->
|
||||
<template v-for="action in availableActions">
|
||||
<el-button
|
||||
:key="action"
|
||||
:type="getActionType(action)"
|
||||
:loading="loading && selectedAction === action"
|
||||
@click="handleAction(action)"
|
||||
>
|
||||
{{ getActionText(action) }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 自定义按钮 -->
|
||||
<template v-for="(button, index) in custom_buttons">
|
||||
<el-button
|
||||
:key="'custom-' + index"
|
||||
:type="button.type || 'default'"
|
||||
:loading="loading && selectedAction === button.action"
|
||||
@click="handleAction(button.action)"
|
||||
>
|
||||
{{ button.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-button-container {
|
||||
/*display: flex;
|
||||
//flex-direction: column;*/
|
||||
min-height: 500px;
|
||||
position: relative;
|
||||
height: 100vh; /* 使用视口高度 */
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表单信息容器 */
|
||||
.form-info-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 流程图和办理记录共用样式 */
|
||||
.process-diagram-container,
|
||||
.process-records-container {
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.display-mode-selector {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 流程图样式 */
|
||||
.process-diagram {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.mock-process-diagram {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.mock-node {
|
||||
padding: 10px 15px;
|
||||
background-color: #f5f7fa;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
margin: 0 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mock-node.start {
|
||||
background-color: #f0f9eb;
|
||||
border-color: #e1f3d8;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.mock-node.end {
|
||||
background-color: #f0f2f5;
|
||||
border-color: #dcdfe6;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.mock-node.active {
|
||||
background-color: #ecf5ff;
|
||||
border-color: #d9ecff;
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mock-arrow {
|
||||
width: 30px;
|
||||
height: 2px;
|
||||
background-color: #dcdfe6;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mock-arrow:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -3px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 4px 0 4px 6px;
|
||||
border-color: transparent transparent transparent #dcdfe6;
|
||||
}
|
||||
|
||||
/* 办理记录样式 */
|
||||
.process-records {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* 时间线模式样式 */
|
||||
.record-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.record-action {
|
||||
color: #409eff;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.record-comment {
|
||||
color: #606266;
|
||||
background-color: #f5f7fa;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 表格模式样式 */
|
||||
.no-comment {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 底部固定操作区域 */
|
||||
.footer-actions {
|
||||
border-top: 1px solid #ebeef5;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
//position: sticky;
|
||||
//bottom: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.comment-container,
|
||||
.current-task-container,
|
||||
.flow-container,
|
||||
.reject-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.comment-label,
|
||||
.current-task-label,
|
||||
.flow-label,
|
||||
.reject-label {
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.current-task-info {
|
||||
padding: 5px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.target-node-info {
|
||||
padding: 5px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<div class="flow-container">
|
||||
<!-- 页面标题栏 -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="title-section">
|
||||
<h2 class="page-title">{{ pageTitle }}</h2>
|
||||
<div class="page-breadcrumb">
|
||||
<span>工作流管理</span>
|
||||
<span class="separator">></span>
|
||||
<span>{{ isNewApplication ? "发起申请" : "任务处理" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-section">
|
||||
<span class="status-label">状态:</span>
|
||||
<span class="status-badge" :class="statusClass">{{ processStatusText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧内容 -->
|
||||
<div class="content-left">
|
||||
<!-- 流程基本信息 -->
|
||||
<div class="info-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">流程信息</h3>
|
||||
<div class="panel-actions">
|
||||
<el-button size="small" type="text" @click="showProcessDiagram" v-if="canShowDiagram">
|
||||
<i class="el-icon-view"></i>
|
||||
查看流程图
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div class="info-table">
|
||||
<!-- 新申请场景 -->
|
||||
<template v-if="isNewApplication">
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程名称:</div>
|
||||
<div class="info-value">{{ processDefinition.displayName || processDefinition.name }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程说明:</div>
|
||||
<div class="info-value">{{ processDefinition.description }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 已有流程场景 -->
|
||||
<template v-else-if="isExistingProcess">
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程名称:</div>
|
||||
<div class="info-value">{{ processInstance.displayName }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">发起人:</div>
|
||||
<div class="info-value">{{ processInstance?.ext?.initiatorName || "-" }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">发起时间:</div>
|
||||
<div class="info-value">{{ formatDate(processInstance.createdAt) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 历史办理过程面板 -->
|
||||
<div v-if="shouldShowHistoryProcess" class="history-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">办理过程</h3>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div id="history-process-container" class="form-content">
|
||||
<div v-if="pjaxLoading.historyProcess" class="loading-state">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>正在加载历史办理过程...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单处理区域 -->
|
||||
<div class="form-panel" v-if="isNewApplication || todoTasks.map((v) => v.id).includes(taskId)">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">{{ formPanelTitle }}</h3>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<!-- 新申请表单 -->
|
||||
<div v-if="isNewApplication" id="application-form-container" class="form-content">
|
||||
<div v-if="pjaxLoading.apply" class="loading-state">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>正在加载申请表单...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前任务表单 -->
|
||||
<div v-if="shouldShowTaskForm" id="task-form-container" class="form-content">
|
||||
<div v-if="pjaxLoading.taskForm" class="loading-state">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>正在加载任务表单...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧审批记录 -->
|
||||
<!-- <div class="content-right" v-if="isExistingProcess">-->
|
||||
<!-- <snaker-flow-history-approval :task_id="taskId"></snaker-flow-history-approval>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<!-- 流程图对话框 -->
|
||||
<el-dialog title="流程图预览" :visible.sync="designVisible" width="80%" top="8vh">
|
||||
<div class="diagram-viewer">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "SnakerFlow",
|
||||
data() {
|
||||
return {
|
||||
// URL参数
|
||||
taskId: GetQueryString("taskId") ? parseInt(GetQueryString("taskId")) : null,
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
businessId: GetQueryString("businessId"),
|
||||
defineKey: GetQueryString("defineKey"), // 新增:流程定义KEY
|
||||
|
||||
processInstance: {},
|
||||
//流程定义信息
|
||||
processDefinition: {},
|
||||
todoTasks: [],
|
||||
currentTask: {},
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 错误信息
|
||||
error: null,
|
||||
|
||||
// PJAX 加载状态
|
||||
pjaxLoading: {
|
||||
apply: false,
|
||||
taskForm: false,
|
||||
historyProcess: false
|
||||
},
|
||||
|
||||
// 显示历史记录
|
||||
showHistory: false,
|
||||
|
||||
// 流程图相关
|
||||
designVisible: false,
|
||||
designUrl: "",
|
||||
|
||||
// 流程状态枚举映射
|
||||
processStatusMap: {
|
||||
10: { text: "进行中", class: "doing" },
|
||||
20: { text: "已完成", class: "finished" },
|
||||
30: { text: "已撤回", class: "withdraw" },
|
||||
40: { text: "强行终止", class: "interrupt" },
|
||||
45: { text: "已拒绝", class: "reject" },
|
||||
50: { text: "挂起", class: "pending" },
|
||||
99: { text: "已废弃", class: "abandon" }
|
||||
},
|
||||
|
||||
// 任务状态枚举
|
||||
taskStateEnum: {
|
||||
DOING: 10,
|
||||
FINISHED: 20,
|
||||
WITHDRAW: 30,
|
||||
INTERRUPT: 40,
|
||||
PENDING: 50,
|
||||
ABANDON: 99
|
||||
},
|
||||
|
||||
// 任务类型枚举
|
||||
performTypeEnum: {
|
||||
NORMAL: 0, // 普通任务
|
||||
COUNTERSIGN: 1 // 会签任务
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 页面标题
|
||||
pageTitle() {
|
||||
if (this.isNewApplication) {
|
||||
return this.processDefinition.displayName || this.processDefinition.name || "发起申请"
|
||||
} else {
|
||||
return this.processInstance.displayName || "任务处理"
|
||||
}
|
||||
},
|
||||
|
||||
// 流程状态文本
|
||||
processStatusText() {
|
||||
if (this.isNewApplication) {
|
||||
return "待提交"
|
||||
} else {
|
||||
return this.processStatusMap[this.processInstance.state]?.text || "未知状态"
|
||||
}
|
||||
},
|
||||
|
||||
// 状态样式类
|
||||
statusClass() {
|
||||
if (this.isNewApplication) {
|
||||
return "status-pending"
|
||||
} else {
|
||||
const statusClass = this.processStatusMap[this.processInstance.state]?.class
|
||||
return `status-${statusClass || "unknown"}`
|
||||
}
|
||||
},
|
||||
|
||||
// 表单面板标题
|
||||
formPanelTitle() {
|
||||
if (this.isNewApplication) {
|
||||
return "申请表单"
|
||||
} else if (this.shouldShowTaskForm) {
|
||||
return this.currentTask.displayName || "任务表单"
|
||||
} else if (this.shouldShowHistoryProcess) {
|
||||
return "办理过程"
|
||||
} else {
|
||||
return "表单信息"
|
||||
}
|
||||
},
|
||||
|
||||
// 是否显示历史办理过程
|
||||
shouldShowHistoryProcess() {
|
||||
// debugger
|
||||
// return this.businessId && this.isTaskInProgress && !this.isFirstTaskNode
|
||||
return true
|
||||
},
|
||||
|
||||
// 是否显示任务表单
|
||||
shouldShowTaskForm() {
|
||||
debugger
|
||||
return this.taskId != null
|
||||
},
|
||||
|
||||
// 是否可以显示流程图
|
||||
canShowDiagram() {
|
||||
return (this.isNewApplication && this.processDefinition.id) || (this.isExistingProcess && this.processInstance.processId)
|
||||
},
|
||||
|
||||
// 判断是否为新申请
|
||||
isNewApplication() {
|
||||
return !this.taskId && !this.instanceId && this.defineKey
|
||||
},
|
||||
|
||||
// 判断是否为已有流程实例
|
||||
isExistingProcess() {
|
||||
return this.taskId || this.instanceId
|
||||
},
|
||||
|
||||
// 判断任务是否进行中
|
||||
isTaskInProgress() {
|
||||
return this.currentTask && this.currentTask.taskState === this.taskStateEnum.DOING
|
||||
},
|
||||
|
||||
// 判断是否为第一个任务节点
|
||||
isFirstTaskNode() {
|
||||
return this.currentTask && this.currentTask.ext && this.currentTask.ext.isFirstTaskNode
|
||||
},
|
||||
|
||||
// 流程图预览URL
|
||||
processDesignUrl() {
|
||||
if (this.isNewApplication && this.processDefinition.id) {
|
||||
return `/flow/define/preview?id=${this.processDefinition.id}`
|
||||
} else if (this.isExistingProcess && this.processInstance.processId) {
|
||||
return `/flow/define/preview?id=${this.processInstance.processId}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initComponent()
|
||||
// 监听
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.type === "task-complete") {
|
||||
this.initComponent()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化组件
|
||||
async initComponent() {
|
||||
this.loading = true
|
||||
|
||||
debugger
|
||||
|
||||
if (this.isNewApplication) {
|
||||
// 新申请:加载流程定义信息
|
||||
await this.loadProcessDefinition()
|
||||
// 加载流程申请表单
|
||||
await this.loadApplicationForm()
|
||||
} else if (this.isExistingProcess) {
|
||||
// 已有流程:加载流程实例信息
|
||||
await this.loadProcessInstance()
|
||||
// 加载任务信息
|
||||
await this.loadCurrentTask()
|
||||
// 如果有businessId且不是第一个任务节点,加载历史办理过程
|
||||
if (this.businessId) {
|
||||
await this.loadHistoryProcess()
|
||||
}
|
||||
} else {
|
||||
console.warn("URL中缺少必要的参数(defineKey、taskId或instanceId),无法初始化组件")
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
// 加载流程定义信息
|
||||
async loadProcessDefinition() {
|
||||
if (!this.defineKey) return
|
||||
|
||||
try {
|
||||
const response = await $.get("/flow/common/defineInfo", { defineKey: this.defineKey })
|
||||
if (response.code === 0) {
|
||||
this.processDefinition = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载流程定义信息失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载申请表单
|
||||
async loadApplicationForm() {
|
||||
debugger
|
||||
if (!this.defineKey || !this.processDefinition.instanceUrl) return
|
||||
|
||||
this.pjaxLoading.apply = true
|
||||
|
||||
$.pjax({
|
||||
url: this.processDefinition.instanceUrl,
|
||||
container: "#application-form-container",
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
})
|
||||
.done(() => {
|
||||
this.pjaxLoading.apply = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.pjaxLoading.apply = false
|
||||
console.log(this.processDefinition.instanceUrl, "申请表单加载失败")
|
||||
})
|
||||
},
|
||||
|
||||
// 加载流程实例信息
|
||||
async loadProcessInstance() {
|
||||
if (!this.instanceId) return
|
||||
const { code, data, msg } = await $.get("/flow/common/instanceInfo", { instanceId: this.instanceId })
|
||||
if (code === 0) {
|
||||
this.processInstance = data.processInstance
|
||||
this.todoTasks = data.todoTasks
|
||||
}
|
||||
},
|
||||
|
||||
// 加载任务信息
|
||||
async loadCurrentTask() {
|
||||
if (!this.taskId) return
|
||||
$.get("/flow/common/taskInfo", { taskId: this.taskId }, (response) => {
|
||||
if (response.code === 0) {
|
||||
this.currentTask = response.data
|
||||
this.loadTaskForm()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载任务表单
|
||||
async loadTaskForm() {
|
||||
if (!this.taskId || !this.currentTask?.taskModel?.form) return
|
||||
|
||||
if (!this.todoTasks.map((v) => v.id).includes(this.taskId)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.pjaxLoading.taskForm = true
|
||||
|
||||
$.pjax({
|
||||
url: this.currentTask.taskModel.form,
|
||||
container: "#task-form-container",
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
})
|
||||
.done(() => {
|
||||
this.pjaxLoading.taskForm = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.pjaxLoading.taskForm = false
|
||||
console.log(this.currentTask.taskModel.form, "任务表单加载失败")
|
||||
})
|
||||
},
|
||||
|
||||
// 加载历史办理过程
|
||||
async loadHistoryProcess() {
|
||||
if (!this.businessId) return
|
||||
|
||||
this.pjaxLoading.historyProcess = true
|
||||
|
||||
$.pjax({
|
||||
url: `/platform/article/common/fullForm?businessId=${this.businessId}`,
|
||||
container: "#history-process-container",
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
})
|
||||
.done(() => {
|
||||
this.pjaxLoading.historyProcess = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.pjaxLoading.historyProcess = false
|
||||
console.log("历史办理过程加载失败")
|
||||
})
|
||||
},
|
||||
|
||||
// 显示流程图
|
||||
showProcessDiagram() {
|
||||
if (this.processDesignUrl) {
|
||||
this.designUrl = this.processDesignUrl
|
||||
this.designVisible = true
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(state) {
|
||||
return this.processStatusMap[state]?.text || "未知状态"
|
||||
},
|
||||
|
||||
// 获取状态样式类
|
||||
getStatusClass(state) {
|
||||
return `status-${this.processStatusMap[state]?.class || "unknown"}`
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return "-"
|
||||
return new Date(dateStr).toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 简洁OA风格样式 */
|
||||
.flow-container {
|
||||
background-color: #f5f6fa;
|
||||
min-height: 100vh;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 页面头部 - 固定在顶部 */
|
||||
.page-header {
|
||||
background: white;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 16px 0;
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 主要内容区域 - 添加顶部间距 */
|
||||
.main-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin-top: 80px; /* 为固定头部留出空间 */
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-section .page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.page-breadcrumb {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.status-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.status-doing {
|
||||
background: #ecf5ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.status-finished {
|
||||
background: #f0f9ff;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-withdraw {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.status-interrupt {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.status-reject {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.content-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 面板样式 */
|
||||
.info-panel,
|
||||
.form-panel,
|
||||
.history-panel,
|
||||
.approval-panel {
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-content.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 信息表格 */
|
||||
.info-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
min-width: 80px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 表单内容 */
|
||||
.form-content {
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #909399;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loading-state i {
|
||||
font-size: 20px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-state span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 流程图查看器 */
|
||||
.diagram-viewer {
|
||||
height: 65vh;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.diagram-viewer iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1024px) {
|
||||
.main-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="approval-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h4>审批记录</h4>
|
||||
</div>
|
||||
<div class="timeline-container" v-if="historyApprovalRecords.length > 0">
|
||||
<div class="timeline-wrapper">
|
||||
<div
|
||||
v-for="(record, index) in historyApprovalRecords"
|
||||
:key="record.id"
|
||||
class="timeline-item">
|
||||
|
||||
<!-- 时间线节点 -->
|
||||
<div class="timeline-node">
|
||||
<div class="timeline-dot" :class="getDotClass(record.taskState)"></div>
|
||||
<div class="timeline-line" v-if="index < historyApprovalRecords.length - 1"></div>
|
||||
</div>
|
||||
|
||||
<!-- 审批内容 -->
|
||||
<div class="timeline-content">
|
||||
<div class="approval-card">
|
||||
<div class="card-header">
|
||||
<span class="task-name">{{ record.displayName }}</span>
|
||||
<span class="approval-time">{{ formatTimestamp(record.finishTime || record.createdAt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="info-item" v-if="record.operator && record.operator !== 'flow.auto'">
|
||||
<span class="label">办理人:</span>
|
||||
<span class="value">{{ getOperatorName(record) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-item" v-if="record.ext && record.ext.submitType">
|
||||
<span class="label">操作:</span>
|
||||
<span class="value submit-type" :class="'submit-' + record.ext.submitType">
|
||||
{{ getSubmitTypeText(record.ext.submitType) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="info-item" v-if="record.ext && record.ext.opinion">
|
||||
<span class="label">意见:</span>
|
||||
<span class="value">{{ record.ext.opinion }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-records">
|
||||
暂无审批记录
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "SnakerFlowHisApproval",
|
||||
props: {
|
||||
task_id: {
|
||||
type: String | Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 历史审批记录
|
||||
historyApprovalRecords: [],
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
|
||||
// 任务状态枚举
|
||||
taskStateEnum: {
|
||||
DOING: 10,
|
||||
FINISHED: 20,
|
||||
WITHDRAW: 30,
|
||||
INTERRUPT: 40,
|
||||
PENDING: 50,
|
||||
ABANDON: 99
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取实例ID(从task_id获取流程实例信息)
|
||||
instanceId() {
|
||||
return GetQueryString("instanceId")
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadHistoryApprovalRecords()
|
||||
},
|
||||
watch: {
|
||||
task_id: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.loadHistoryApprovalRecords()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载历史审批记录
|
||||
async loadHistoryApprovalRecords() {
|
||||
if (!this.instanceId && !this.task_id) return
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const params = this.instanceId ?
|
||||
{ instanceId: this.instanceId } :
|
||||
{ taskId: this.task_id }
|
||||
|
||||
const response = await $.post("/flow/common/approvalRecord", params)
|
||||
if (response.code === 0) {
|
||||
this.historyApprovalRecords = response.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载历史审批记录失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间戳
|
||||
formatTimestamp(timestamp) {
|
||||
if (!timestamp) return ""
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
})
|
||||
},
|
||||
|
||||
// 获取操作人姓名
|
||||
getOperatorName(record) {
|
||||
if (!record.operator || record.operator === 'flow.auto') {
|
||||
return "系统自动"
|
||||
}
|
||||
if (record.ext && record.ext.operatorName) {
|
||||
return record.ext.operatorName
|
||||
}
|
||||
return record.operator
|
||||
},
|
||||
|
||||
// 获取提交类型文本
|
||||
getSubmitTypeText(submitType) {
|
||||
const typeMap = {
|
||||
'agree': '同意',
|
||||
'reject': '拒绝',
|
||||
'back': '退回',
|
||||
'transfer': '转办',
|
||||
'delegate': '委托',
|
||||
'submit': '提交'
|
||||
}
|
||||
return typeMap[submitType] || submitType
|
||||
},
|
||||
|
||||
// 获取节点样式类
|
||||
getDotClass(state) {
|
||||
switch (state) {
|
||||
case this.taskStateEnum.FINISHED:
|
||||
return "dot-success"
|
||||
case this.taskStateEnum.WITHDRAW:
|
||||
return "dot-warning"
|
||||
case this.taskStateEnum.INTERRUPT:
|
||||
case this.taskStateEnum.ABANDON:
|
||||
return "dot-danger"
|
||||
case this.taskStateEnum.PENDING:
|
||||
return "dot-info"
|
||||
case this.taskStateEnum.DOING:
|
||||
return "dot-primary"
|
||||
default:
|
||||
return "dot-default"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.approval-sidebar {
|
||||
width: 320px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
height: fit-content;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 12px 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-header h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.timeline-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.timeline-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-node {
|
||||
position: relative;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fff;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dot-success {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.dot-warning {
|
||||
background: #faad14;
|
||||
}
|
||||
|
||||
.dot-danger {
|
||||
background: #f5222d;
|
||||
}
|
||||
|
||||
.dot-primary {
|
||||
background: #1890ff;
|
||||
}
|
||||
|
||||
.dot-info {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
.dot-default {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
|
||||
.timeline-line {
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
flex: 1;
|
||||
margin-top: 4px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.approval-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.approval-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
min-width: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.submit-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submit-agree {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.submit-reject {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.submit-back {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.submit-transfer, .submit-delegate, .submit-submit {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.no-records {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.timeline-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.timeline-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.timeline-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
}
|
||||
|
||||
.timeline-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div class="snaker-task-action" v-if="!loading || taskInfo">
|
||||
<!-- 第一个任务节点或新申请:显示提交、保存草稿、取消 -->
|
||||
<template v-if="isFirstTaskNodeOrNew">
|
||||
<el-button type="primary" @click="handleSubmit" :loading="actionLoading" icon="el-icon-upload2">提交</el-button>
|
||||
<!-- 保存草稿只在流程未发起时显示 -->
|
||||
<el-button @click="handleSaveDraft" v-if="!actionLoading && show_save_draft && !instanceId" icon="el-icon-document">保存草稿</el-button>
|
||||
<el-button @click="handleCancel" :loading="actionLoading" v-if="show_cancel" icon="el-icon-close">取消</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 其他任务节点:显示审批按钮 -->
|
||||
<template v-else>
|
||||
<!-- 会签任务按钮 -->
|
||||
<template v-if="is_countersign_task">
|
||||
<el-button type="primary" @click="handleAction('AGREE')" :loading="actionLoading" icon="el-icon-check">同意</el-button>
|
||||
<el-button type="danger" plain @click="handleAction('COUNTERSIGN_DISAGREE')" :loading="actionLoading" icon="el-icon-close">
|
||||
不同意
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 普通任务按钮 -->
|
||||
<template v-else>
|
||||
<el-button type="primary" @click="handleAction('AGREE')" :loading="actionLoading" icon="el-icon-check">同意</el-button>
|
||||
<el-button type="danger" plain @click="handleAction('REJECT')" :loading="actionLoading" icon="el-icon-close">拒绝</el-button>
|
||||
<el-button @click="handleAction('ROLLBACK')" :loading="actionLoading" icon="el-icon-back">退回上一步</el-button>
|
||||
<el-button @click="handleAction('ROLLBACK_TO_OPERATOR')" :loading="actionLoading" icon="el-icon-d-arrow-left">退回发起人</el-button>
|
||||
<el-button @click="handleAction('JUMP')" :loading="actionLoading" icon="el-icon-position">跳转</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-else class="snaker-task-action loading-state">
|
||||
<el-button loading icon="el-icon-loading">加载中...</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "SnakerFlowTaskFormAction",
|
||||
props: {
|
||||
// 是否显示保存草稿按钮
|
||||
show_save_draft: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
show_cancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 操作按钮加载状态
|
||||
actionLoading: false,
|
||||
// 任务信息
|
||||
taskInfo: null,
|
||||
// 任务操作枚举
|
||||
actionEnum: {
|
||||
APPLY: "0", // 发起申请
|
||||
AGREE: "1", // 同意
|
||||
REJECT: "2", // 拒绝
|
||||
COUNTERSIGN_DISAGREE: "20", // 会签不同意 拒绝申请
|
||||
ROLLBACK: "3", // 退回上一步
|
||||
ROLLBACK_TO_OPERATOR: "6", // 退回发起人
|
||||
JUMP: "4", // 跳转
|
||||
RE_APPLY: "5", // 重新提交
|
||||
SUBMIT: "0" // 发起申请
|
||||
},
|
||||
// 任务类型枚举
|
||||
performTypeEnum: {
|
||||
NORMAL: 0, // 普通任务
|
||||
COUNTERSIGN: 1 // 会签任务
|
||||
},
|
||||
|
||||
taskId: GetQueryString("taskId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
defineKey: GetQueryString("defineKey"),
|
||||
defineId: GetQueryString("defineId"),
|
||||
businessId: GetQueryString("businessId")
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否为会签任务
|
||||
is_countersign_task() {
|
||||
return this.taskInfo && this.taskInfo.performType === this.performTypeEnum.COUNTERSIGN
|
||||
},
|
||||
|
||||
// 是否为第一个任务节点或新申请
|
||||
isFirstTaskNodeOrNew() {
|
||||
// 没有任务ID,说明是新申请
|
||||
if (!this.taskId) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 有任务ID但是任务信息中标识为第一个节点
|
||||
return this.taskInfo && this.taskInfo.ext && this.taskInfo.ext.isFirstTaskNode
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 只有当有任务ID时才加载任务信息
|
||||
if (this.taskId) {
|
||||
this.loadTaskInfo()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载任务信息
|
||||
loadTaskInfo() {
|
||||
if (!this.taskId) return
|
||||
|
||||
this.loading = true
|
||||
this.taskInfo = null
|
||||
|
||||
$.get("/flow/common/taskInfo", {
|
||||
taskId: this.taskId
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.taskInfo = res.data
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 处理提交操作
|
||||
handleSubmit() {
|
||||
// 根据是否有流程实例来确定操作类型
|
||||
// const submitAction = this.instanceId ? this.actionEnum.RE_APPLY : this.actionEnum.APPLY
|
||||
const submitAction = this.actionEnum.APPLY
|
||||
|
||||
// 触发父组件事件,传递提交操作
|
||||
this.$emit("task-action", {
|
||||
submitType: submitAction,
|
||||
taskId: this.taskId,
|
||||
instanceId: this.instanceId,
|
||||
defineId: this.defineId,
|
||||
defineKey: this.defineKey,
|
||||
businessId: this.businessId
|
||||
// currentTask: this.taskInfo
|
||||
})
|
||||
},
|
||||
|
||||
// 处理任务操作
|
||||
handleAction(actionKey) {
|
||||
if (this.actionLoading || !this.taskInfo) return
|
||||
|
||||
// 触发父组件事件,传递操作类型和任务信息
|
||||
this.$emit("task-action", {
|
||||
submitType: this.actionEnum[actionKey],
|
||||
taskId: this.taskId,
|
||||
processTaskId: this.taskId,
|
||||
instanceId: this.instanceId,
|
||||
defineId: this.defineId,
|
||||
defineKey: this.defineKey,
|
||||
businessId: this.businessId
|
||||
})
|
||||
},
|
||||
|
||||
// 保存草稿
|
||||
handleSaveDraft() {
|
||||
if (this.actionLoading) return
|
||||
|
||||
// 保存草稿时不显示加载状态,因为只是保存业务数据
|
||||
this.$emit("save-draft", {
|
||||
defineId: this.defineId,
|
||||
businessId: this.businessId,
|
||||
isDraft: true // 标识这是草稿保存
|
||||
})
|
||||
},
|
||||
|
||||
// 取消操作
|
||||
handleCancel() {
|
||||
this.$emit("cancel")
|
||||
},
|
||||
|
||||
// 刷新任务信息
|
||||
refresh() {
|
||||
if (this.taskId) {
|
||||
this.loadTaskInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.snaker-task-action {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.snaker-task-action .el-button {
|
||||
margin: 0 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.snaker-task-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.snaker-task-action .el-button {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="item in options">
|
||||
<el-tag :key="item[value_key]" v-if="item[value_key] === value" v-bind="$attrs">{{ item[label_key] }}</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 全局缓存和请求Promise缓存
|
||||
const enumCache = {}
|
||||
const requestPromises = {}
|
||||
|
||||
module.exports = {
|
||||
name: "DictTag",
|
||||
props: {
|
||||
value: { type: String | Number },
|
||||
name: { type: String },
|
||||
value_key: {
|
||||
type: String,
|
||||
default: "code"
|
||||
},
|
||||
label_key: {
|
||||
type: String,
|
||||
default: "name"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
code:{
|
||||
handler(val) {
|
||||
this.getEnumOptions()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getEnumOptions() {
|
||||
// // 检查数据缓存
|
||||
// if (enumCache[this.name]) {
|
||||
// this.options = enumCache[this.name]
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 检查是否已有相同请求在进行中
|
||||
// if (requestPromises[this.name]) {
|
||||
// requestPromises[this.name].then(data => {
|
||||
// this.options = data
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
// 创建请求Promise并缓存
|
||||
requestPromises[this.name] = $.get("/open/common/dictEnumOptions", { name: this.name })
|
||||
.then(res => {
|
||||
if (res.code === 0) {
|
||||
// 存入数据缓存
|
||||
enumCache[this.name] = res.data
|
||||
// 清除请求Promise缓存
|
||||
delete requestPromises[this.name]
|
||||
return res.data
|
||||
}
|
||||
})
|
||||
|
||||
requestPromises[this.name].then(data => {
|
||||
this.options = data
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-tag + .el-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,520 +1,323 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="${AppName!}" />
|
||||
<title>${AppName!}</title>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=1.0.1" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/a.css?v=1.0.0" />
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
<style>
|
||||
.sub-app-container {
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
min-height: calc(100vh - 64px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
.sidebar-menu {
|
||||
width: 250px;
|
||||
background: rgb(35 85 120);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
height: calc(100vh - 64px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/nprogress/0.2.0/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
<!-- Array数组增加findIndex等方法,TreeTable用得到,兼容IE8 -->
|
||||
<script src="${base!}/assets/platform/plugins/polyfill/polyfill.js"></script>
|
||||
<!-- 时间日期处理工具类 -->
|
||||
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/toastr/toastr.js"></script>
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/toastr/toastr.css" />
|
||||
<!--axios-->
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
|
||||
<!--图片预览-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
|
||||
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
|
||||
<!--签字canvas-->
|
||||
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
|
||||
<!--二维码-->
|
||||
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css" />
|
||||
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
<script src="${base!}/components/plugins/sysIconSelector/iconSelect.js"></script>
|
||||
<!--MinDash-->
|
||||
<script src="${base!}/assets/platform/plugins/min-dash/min-dash.js"></script>
|
||||
<!--Sortable-->
|
||||
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
|
||||
<!--vxe-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-table.css" />
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/xe-utils.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vue-table.js"></script>-->
|
||||
.menu-header {
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/style.min.css" />-->
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-table@3.9.0/lib/style.min.css" />-->
|
||||
<script src="https://vxeui.com/umd/xe-utils@3.5.30/dist/xe-utils.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/index.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-table@3.9.0/lib/index.umd.min.js"></script>
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
#sidebar-menu .el-scrollbar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
#sidebar-menu .el-scrollbar .el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
<script>
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
|
||||
}
|
||||
</script>
|
||||
<!-- <script-->
|
||||
<!-- type="text/javascript"-->
|
||||
<!-- src="https://webapi.amap.com/maps?v=2.0&key=57f0d098ba1b881ecc436c4cfd23bbbf&plugin=AMap.PolyEditor,AMap.Geolocation,AMap.PlaceSearch"-->
|
||||
<!-- ></script>-->
|
||||
#sidebar-menu .el-menu {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.config.devtools = false
|
||||
//ELEMENTUI
|
||||
ELEMENT.locale(ELEMENT.lang["${lang,escape}"])
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
})
|
||||
#sidebar-menu .el-menu .el-submenu__title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
Vue.use(FcDesigner)
|
||||
Vue.use(FcDesigner.formCreate)
|
||||
#sidebar-menu .el-menu .el-submenu__title i,
|
||||
#sidebar-menu .el-menu .el-menu-item i {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// formCreate.create([], {
|
||||
// submitBtn: {
|
||||
// show: false
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
#sidebar-menu .el-menu .el-submenu__title:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
<!--ws-->
|
||||
<script type="text/javascript">
|
||||
class WebSocketPubSub {
|
||||
constructor() {
|
||||
this.ws = null
|
||||
this.subscribers = new Map()
|
||||
this.isSubscribed = false // 添加订阅状态标记
|
||||
this.init()
|
||||
}
|
||||
#sidebar-menu .el-menu .el-menu-item {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
this.setupPing()
|
||||
this.setupWindowEvents()
|
||||
} catch (ex) {
|
||||
console.info(ex)
|
||||
}
|
||||
}
|
||||
#sidebar-menu .el-menu .el-menu-item.is-active {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
connect() {
|
||||
const WS_URL = window.location.host + "${base}/websocket"
|
||||
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
|
||||
this.ws = new WebSocket(protocol + WS_URL)
|
||||
}
|
||||
#sidebar-menu .el-menu .el-menu-item:focus,
|
||||
.el-menu-item:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
//订阅
|
||||
subscribe(type, callback) {
|
||||
if (!this.subscribers.has(type)) {
|
||||
this.subscribers.set(type, new Set())
|
||||
}
|
||||
this.subscribers.get(type).add(callback)
|
||||
#sidebar-menu .el-menu .el-menu-item a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.ws.readyState)
|
||||
// 所有订阅完成后,发送 join 消息
|
||||
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.isSubscribed = true
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
.sub-app-container-main-content {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background: #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
setupEventListeners() {
|
||||
// 消息处理
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
// 触发订阅者的回调
|
||||
if (this.subscribers.has(data.action)) {
|
||||
this.subscribers.get(data.action).forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error("订阅者回调执行错误:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("消息解析错误:", error)
|
||||
}
|
||||
}
|
||||
.content-header {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
// 连接建立
|
||||
this.ws.onopen = () => {
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// room: store.state.room,
|
||||
// action: "join"
|
||||
// })
|
||||
// )
|
||||
// 连接建立时不立即发送 join,等待订阅完成
|
||||
this.ping()
|
||||
}
|
||||
}
|
||||
.content-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 新增发送 join 消息的方法
|
||||
sendJoinMessage() {
|
||||
this.send({
|
||||
room: store.state.room,
|
||||
action: "join"
|
||||
})
|
||||
}
|
||||
.content-subtitle {
|
||||
color: #7f8c8d;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
setupPing() {
|
||||
setInterval(() => this.ping(), (11624317 / 1000) * 2)
|
||||
}
|
||||
.content-body {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
ping() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send("{}")
|
||||
} catch (e) {
|
||||
this.reconnect()
|
||||
}
|
||||
} else {
|
||||
this.reconnect()
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.sub-app-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close() // 确保旧连接已关闭
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
// 如果之前已订阅,重新发送join消息
|
||||
if (this.isSubscribed) {
|
||||
setTimeout(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}, 3000) // 延迟3秒重连
|
||||
}
|
||||
.sidebar-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 窗口事件处理
|
||||
setupWindowEvents() {
|
||||
let _beforeUnload_time = 0
|
||||
window.onbeforeunload = () => {
|
||||
_beforeUnload_time = new Date().getTime()
|
||||
}
|
||||
window.onunload = () => {
|
||||
const _gap_time = new Date().getTime() - _beforeUnload_time
|
||||
if (_gap_time <= 5) {
|
||||
this.send({
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
action: "left"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.sub-app-container-main-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
send(message) {
|
||||
if (this.ws) {
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message)
|
||||
this.ws.send(messageStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.webSocketPubSub = new WebSocketPubSub()
|
||||
</script>
|
||||
|
||||
<!--vuex-->
|
||||
<script type="text/javascript">
|
||||
console.log(${json(@auth.getPrincipalProperty('birthday'))})
|
||||
|
||||
window.sessionStorage.setItem("user",JSON.stringify(${json(@auth.getLogonUser())}))
|
||||
window.store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: "zhgh-vuex",
|
||||
paths: ["openedMenus", "activeMenuIndex", "activeMenuModuleId"],
|
||||
storage: window.localStorage
|
||||
})
|
||||
],
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user")),
|
||||
//当前激活的菜单
|
||||
activeMenuIndex: null,
|
||||
//打开菜单的数组
|
||||
openedMenus: [],
|
||||
//激活的菜单模块
|
||||
activeMenuModuleId: null,
|
||||
//点击过的菜单(历史标签页使用)
|
||||
historyMenuIds: [],
|
||||
//消息列表
|
||||
messages: [],
|
||||
//聊天室
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
//签字
|
||||
signature: {}
|
||||
},
|
||||
getters: {
|
||||
fullMenus: (state) => {
|
||||
return state.user.menus
|
||||
},
|
||||
pcModules: (state) => {
|
||||
return state.user.pcModuleMenus
|
||||
},
|
||||
historyMenuIds: (state) => {
|
||||
return state.historyMenuIds
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
//设置用户信息
|
||||
setUser(state, payload) {
|
||||
state.user = payload
|
||||
},
|
||||
//设置激活的菜单
|
||||
setActiveMenuIndex(state, payload) {
|
||||
state.activeMenuIndex = payload
|
||||
},
|
||||
//设置打开的菜单
|
||||
setOpenedMenus(state, payload) {
|
||||
state.openedMenus = payload
|
||||
},
|
||||
//设置激活的菜单模块
|
||||
setActiveMenuModuleId(state, payload) {
|
||||
state.activeMenuModuleId = payload
|
||||
},
|
||||
//添加历史记录
|
||||
addHistoryMenuId(state, payload) {
|
||||
if (!payload) {
|
||||
return
|
||||
}
|
||||
const exists = state.historyMenuIds.includes(payload)
|
||||
if (!exists) {
|
||||
state.historyMenuIds.push(payload)
|
||||
}
|
||||
},
|
||||
//移除某条历史记录
|
||||
removeHistoryMenuId(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => id !== payload)
|
||||
},
|
||||
//移除多条历史记录
|
||||
removeHistoryMenuIds(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => !payload.includes(id))
|
||||
},
|
||||
//清空历史记录
|
||||
clearHistoryMenuIds(state) {
|
||||
state.historyMenuIds = []
|
||||
state.activeMenuIndex = null
|
||||
state.openedMenus = []
|
||||
pjaxReplace("/platform/home")
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
pjaxRoute({ commit, dispatch, state, getters }, href) {
|
||||
const { pathname, search } = new URL(href, window.location.origin)
|
||||
const matchMenu = getters.fullMenus.find((menu) => menu.href === pathname)
|
||||
if (!matchMenu) {
|
||||
commit("setActiveMenuIndex", null)
|
||||
commit("setOpenedMenus", [])
|
||||
if (!state.activeModuleId && getters.pcModules) {
|
||||
commit("setActiveMenuModuleId", getters.pcModules[0].id)
|
||||
}
|
||||
pjaxReplace(pathname === "/platform/home" ? pathname : pathname)
|
||||
return
|
||||
}
|
||||
//拷贝一份
|
||||
let cloneMenu = JSON.parse(JSON.stringify(matchMenu))
|
||||
//找出所有父菜单包括自己
|
||||
let parentMenus = []
|
||||
while (cloneMenu && cloneMenu.parentId) {
|
||||
cloneMenu = getters.fullMenus.find((m) => m.id === cloneMenu.parentId)
|
||||
if (cloneMenu) {
|
||||
parentMenus.unshift(cloneMenu)
|
||||
}
|
||||
}
|
||||
parentMenus.push(matchMenu)
|
||||
|
||||
//获取到该菜单所属的模块 从一级菜单获取
|
||||
const moduleId = parentMenus[0]?.moduleId
|
||||
//判断下是否跨模块跳转
|
||||
if (moduleId !== state.activeMenuModuleId) {
|
||||
commit("setActiveMenuModuleId", moduleId)
|
||||
}
|
||||
Vue.nextTick(()=>{
|
||||
//重新设置展开菜单
|
||||
commit("setOpenedMenus", parentMenus.map((menu) => menu.id))
|
||||
//设置当前激活菜单
|
||||
commit("setActiveMenuIndex", matchMenu.id)
|
||||
//判断是否加入到历史点击菜单
|
||||
commit("addHistoryMenuId", matchMenu.id)
|
||||
//跳转
|
||||
pjaxReplace(matchMenu.href ? matchMenu.href + (search || "") : "/platform/home")
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--beetl全局变量方法-->
|
||||
<script type="text/javascript">
|
||||
const base = "${base!}"
|
||||
const APP_DOMAIN = "${AppDomain!}"
|
||||
</script>
|
||||
|
||||
<!--vue挂载-->
|
||||
<script type="text/javascript">
|
||||
Vue.prototype.$moment = moment
|
||||
Vue.prototype.$businessTool = businessTool
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
Vue.prototype.$downLoad = commonUtil.downLoadService
|
||||
</script>
|
||||
|
||||
<!--vue全局组件-->
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
Vue.component("guava", httpVueLoader("/components/plugins/Guava.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-select", httpVueLoader("/components/plugins/sysDict/DictSelect.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-upload", httpVueLoader("/components/plugins/sysUpload/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-select", httpVueLoader("/components/plugins/sysUserSelect/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("table-tool", httpVueLoader("/components/plugins/sysTableTool/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("signature", httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("pc-signature", httpVueLoader("/components/plugins/sysSignature/pc.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search", httpVueLoader("/components/plugins/sysPageFormSearch/search.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-item", httpVueLoader("/components/plugins/sysPageFormSearch/searchItem.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-query", httpVueLoader("/components/plugins/sysPageFormSearch/searchQuery.vue?v=" + new Date().getTime()))
|
||||
Vue.component("el-spin", httpVueLoader("/components/plugins/spin/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-opinion-textarea", httpVueLoader("/components/plugins/sysUserAuditOpinion/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-audit", httpVueLoader("/components/plugins/flow/audit/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("icon-selector", httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="ele-admin-layout ele-admin-fixed-body ele-admin-side-dark ele-admin-show-tabs ele-admin-responsive">
|
||||
<div class="ele-admin-header" id="ele-admin-header" v-cloak>
|
||||
<div class="ele-admin-logo">
|
||||
<img src="${AppLogo!}" alt="logo" @click="$store.dispatch('pjaxRoute','/platform/home')" />
|
||||
</div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<div class="ele-admin-header-tool-item collapse-menu" style="color: var(--color-white)" @click="collapseMenu">
|
||||
<i class="el-icon-s-unfold"></i>
|
||||
</div>
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<!--菜单模块-->
|
||||
<header-menu-module></header-menu-module>
|
||||
</div>
|
||||
</div>
|
||||
<!--面包屑-->
|
||||
<div class="ele-admin-breadcrumb">
|
||||
<header-bread-crumb></header-bread-crumb>
|
||||
</div>
|
||||
<div class="ele-admin-header-nav"></div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<!--消息通知-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-message></header-message>
|
||||
</div>
|
||||
<!--个人中心/退出-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-user-avatar></header-user-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-admin-main">
|
||||
<div class="ele-admin-sidebar" id="ele-admin-sidebar" v-cloak>
|
||||
<sidebar-left-menu></sidebar-left-menu>
|
||||
</div>
|
||||
<div class="ele-admin-body">
|
||||
<div id="ele-admin-tabs">
|
||||
<header-tab-page></header-tab-page>
|
||||
</div>
|
||||
<div class="ele-admin-content">
|
||||
<div class="ele-admin-content-view">
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ele-admin-shade"
|
||||
onclick="document.querySelector('body >div > div.ele-admin-layout').classList.add('ele-admin-collapse')"
|
||||
></div>
|
||||
</div>
|
||||
<div class="sub-app-container" id="sub-app-container">
|
||||
<!-- 左侧菜单区域 -->
|
||||
<nav class="sidebar-menu" id="sidebar-menu">
|
||||
<div class="menu-header">
|
||||
<h2 class="menu-title">子系统</h2>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
<!--左侧菜单 && 头部 && 标签页-->
|
||||
new Vue({
|
||||
el: "#ele-admin-sidebar",
|
||||
components: {
|
||||
"sidebar-left-menu": httpVueLoader("/components/layout/SideBarLeftMenu.vue?v=" + new Date().getTime())
|
||||
}
|
||||
|
||||
<el-scrollbar>
|
||||
<el-menu unique-opened :default-active="activeMenuIndex" :default-openeds="openedMenus" @select="menuSelect">
|
||||
<menu-list :menus="leftMenus" :depth="0"></menu-list>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</nav>
|
||||
|
||||
<!-- 右侧主体区域 -->
|
||||
<main class="sub-app-container-main-content" id="sub-app-container-main-content">
|
||||
<!-- <header class="content-header">-->
|
||||
<!-- <h1 class="content-title"></h1>-->
|
||||
<!-- <!– <p class="content-subtitle"></p>–>-->
|
||||
<!-- </header>-->
|
||||
|
||||
<div class="content-body" id="sub-app-container-main-content-body">${layoutContent!}</div>
|
||||
</main>
|
||||
|
||||
<!-- 子应用ID -->
|
||||
<div id="sub-app-id" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 初始化pjax加载
|
||||
;(function initPjax() {
|
||||
$(document).pjax("#sidebar-menu a[data-pjax]", "#sub-app-container-main-content-body", {
|
||||
maxCacheLength: 0,
|
||||
push: true,
|
||||
replace: false,
|
||||
fragment: "#sub-app-container-main-content-body",
|
||||
timeout: 8000
|
||||
})
|
||||
|
||||
$(document).on("pjax:send", function () {
|
||||
NProgress.configure({ parent: ".sub-app-container-main-content" })
|
||||
NProgress.start()
|
||||
$("#sub-app-container-main-content").hide()
|
||||
})
|
||||
|
||||
$(document).on("pjax:complete", function () {
|
||||
NProgress.done()
|
||||
$("#sub-app-container-main-content").show()
|
||||
setContentTitle()
|
||||
})
|
||||
|
||||
setContentTitle()
|
||||
})()
|
||||
|
||||
// 设置内容标题
|
||||
function setContentTitle() {
|
||||
// const menu = store.state.user.menus.find((v) => v.href === window.location.pathname)
|
||||
// $("#sub-app-container-main-content .content-title").text(menu?.name)
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
$("#sub-app-container #sidebar-menu .menu-header .menu-title").text(app?.name)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const menuLinks = document.querySelectorAll("#sidebar-menu a")
|
||||
menuLinks.forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
const pjax = this.hasAttribute("data-pjax")
|
||||
e.preventDefault()
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-header",
|
||||
store,
|
||||
components: {
|
||||
"header-message": httpVueLoader("/components/layout/HeaderMessage.vue?v=" + new Date().getTime()),
|
||||
"header-bread-crumb": httpVueLoader("/components/layout/HeaderBreadCrumb.vue?v=" + new Date().getTime()),
|
||||
"header-menu-module": httpVueLoader("/components/layout/HeaderMenuModule.vue?v=" + new Date().getTime()),
|
||||
"header-user-avatar": httpVueLoader("/components/layout/HeaderUserAvatar.vue?v=" + new Date().getTime())
|
||||
},
|
||||
methods: {
|
||||
collapseMenu() {
|
||||
document.querySelector("body >div > div.ele-admin-layout").classList.remove("ele-admin-collapse")
|
||||
})
|
||||
})
|
||||
|
||||
new Vue({
|
||||
el: "#sidebar-menu",
|
||||
components: {
|
||||
"menu-list": {
|
||||
name: "menu-list",
|
||||
template:
|
||||
/*language=HTML*/
|
||||
`
|
||||
<div>
|
||||
<template v-for="(item, index) in menus">
|
||||
<el-submenu :index="item.id" v-if="item.children && item.children.length > 0">
|
||||
<template #title>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else class="el-icon-paperclip"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</template>
|
||||
<menu-list :menus="item.children" :depth="depth + 1" />
|
||||
</el-submenu>
|
||||
<el-menu-item :index="item.id" v-else>
|
||||
<a :href="item.href" data-pjax>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else :class="item.icon ? item.icon : 'el-icon-menu'"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
props: ["menus", "depth"]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menus: [],
|
||||
activeMenuIndex: null,
|
||||
openedMenus: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
leftMenus() {
|
||||
if (this.menus) {
|
||||
return this.menus
|
||||
} else {
|
||||
try {
|
||||
return JSON.parse(window.sessionStorage.getItem("zhgh_sub_app_menus"))
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-tabs",
|
||||
components: {
|
||||
"header-tab-page": httpVueLoader("/components/layout/HeaderTabPage.vue?v=" + new Date().getTime())
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
menuSelect(index, indexPath) {
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
window.sessionStorage.setItem("zhgh_sub_app_left_menu_active_index-" + app.id, index)
|
||||
window.sessionStorage.setItem("zhgh_sub_app_left_menu_opens-" + app.id, JSON.stringify(indexPath))
|
||||
} catch (e) {}
|
||||
},
|
||||
// 获取菜单
|
||||
getMenus(appId) {
|
||||
if (!appId) return
|
||||
$.get("/platform/sys/user/subAppMenus", { appId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.menus = res.data
|
||||
window.sessionStorage.setItem("zhgh_sub_app_menus", JSON.stringify(res.data))
|
||||
} else {
|
||||
this.menus = []
|
||||
}
|
||||
this.echoMenus()
|
||||
})
|
||||
},
|
||||
|
||||
setAppInfo(app) {
|
||||
if (app) {
|
||||
// 储存到缓存
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
|
||||
// 储存到div中
|
||||
document.getElementById("sub-app-id").innerText = app.id
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
// document.querySelector("#sub-app-container .content-title").innerText = app.name
|
||||
},
|
||||
|
||||
// 菜单回显
|
||||
echoMenus() {
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
this.activeMenuIndex = window.sessionStorage.getItem("zhgh_sub_app_left_menu_active_index-" + app.id)
|
||||
this.openedMenus = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app_left_menu_opens-" + app.id)) || []
|
||||
console.log(this.openedMenus)
|
||||
console.log(this.activeMenuIndex)
|
||||
} catch (e) {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时获取到正确的菜单
|
||||
const pathname = window.location.pathname
|
||||
if (!pathname.startsWith("/platform/v4/subApp")) {
|
||||
$.get("/platform/sys/user/rootMenuByPath", { pathname }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.setAppInfo(res.data)
|
||||
this.getMenus(res.data?.id)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const appId = GetQueryString("appId")
|
||||
if (!appId) {
|
||||
return
|
||||
}
|
||||
this.getMenus(appId)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="${AppName!}" />
|
||||
<title>${AppName!}</title>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=1.0.1" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/a.css?v=1.0.0" />
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/nprogress/0.2.0/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
<!-- Array数组增加findIndex等方法,TreeTable用得到,兼容IE8 -->
|
||||
<script src="${base!}/assets/platform/plugins/polyfill/polyfill.js"></script>
|
||||
<!-- 时间日期处理工具类 -->
|
||||
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/toastr/toastr.js"></script>
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/toastr/toastr.css" />
|
||||
<!--axios-->
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
|
||||
<!--图片预览-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
|
||||
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
|
||||
<!--签字canvas-->
|
||||
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
|
||||
<!--二维码-->
|
||||
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css" />
|
||||
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
<script src="${base!}/components/plugins/sysIconSelector/iconSelect.js"></script>
|
||||
<!--MinDash-->
|
||||
<script src="${base!}/assets/platform/plugins/min-dash/min-dash.js"></script>
|
||||
<!--Sortable-->
|
||||
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
|
||||
<!--vxe-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-table.css" />
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/xe-utils.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.js"></script>-->
|
||||
<!-- <script src="${base!}/assets/platform/plugins/vxe/vue-table.js"></script>-->
|
||||
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/style.min.css" />-->
|
||||
<!-- <link rel="stylesheet" href="https://vxeui.com/umd/vxe-table@3.9.0/lib/style.min.css" />-->
|
||||
<script src="https://vxeui.com/umd/xe-utils@3.5.30/dist/xe-utils.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/index.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-table@3.9.0/lib/index.umd.min.js"></script>
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
|
||||
<script>
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
|
||||
}
|
||||
</script>
|
||||
<!-- <script-->
|
||||
<!-- type="text/javascript"-->
|
||||
<!-- src="https://webapi.amap.com/maps?v=2.0&key=57f0d098ba1b881ecc436c4cfd23bbbf&plugin=AMap.PolyEditor,AMap.Geolocation,AMap.PlaceSearch"-->
|
||||
<!-- ></script>-->
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.config.devtools = false
|
||||
//ELEMENTUI
|
||||
ELEMENT.locale(ELEMENT.lang["${lang,escape}"])
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
})
|
||||
|
||||
Vue.use(FcDesigner)
|
||||
Vue.use(FcDesigner.formCreate)
|
||||
|
||||
// formCreate.create([], {
|
||||
// submitBtn: {
|
||||
// show: false
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
|
||||
<!--ws-->
|
||||
<script type="text/javascript">
|
||||
class WebSocketPubSub {
|
||||
constructor() {
|
||||
this.ws = null
|
||||
this.subscribers = new Map()
|
||||
this.isSubscribed = false // 添加订阅状态标记
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
this.setupPing()
|
||||
this.setupWindowEvents()
|
||||
} catch (ex) {
|
||||
console.info(ex)
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
const WS_URL = window.location.host + "${base}/websocket"
|
||||
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
|
||||
this.ws = new WebSocket(protocol + WS_URL)
|
||||
}
|
||||
|
||||
//订阅
|
||||
subscribe(type, callback) {
|
||||
if (!this.subscribers.has(type)) {
|
||||
this.subscribers.set(type, new Set())
|
||||
}
|
||||
this.subscribers.get(type).add(callback)
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.ws.readyState)
|
||||
// 所有订阅完成后,发送 join 消息
|
||||
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.isSubscribed = true
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
setupEventListeners() {
|
||||
// 消息处理
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
// 触发订阅者的回调
|
||||
if (this.subscribers.has(data.action)) {
|
||||
this.subscribers.get(data.action).forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error("订阅者回调执行错误:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("消息解析错误:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 连接建立
|
||||
this.ws.onopen = () => {
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// room: store.state.room,
|
||||
// action: "join"
|
||||
// })
|
||||
// )
|
||||
// 连接建立时不立即发送 join,等待订阅完成
|
||||
this.ping()
|
||||
}
|
||||
}
|
||||
|
||||
// 新增发送 join 消息的方法
|
||||
sendJoinMessage() {
|
||||
this.send({
|
||||
room: store.state.room,
|
||||
action: "join"
|
||||
})
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
setupPing() {
|
||||
setInterval(() => this.ping(), (11624317 / 1000) * 2)
|
||||
}
|
||||
|
||||
ping() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send("{}")
|
||||
} catch (e) {
|
||||
this.reconnect()
|
||||
}
|
||||
} else {
|
||||
this.reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close() // 确保旧连接已关闭
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
// 如果之前已订阅,重新发送join消息
|
||||
if (this.isSubscribed) {
|
||||
setTimeout(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}, 3000) // 延迟3秒重连
|
||||
}
|
||||
|
||||
// 窗口事件处理
|
||||
setupWindowEvents() {
|
||||
let _beforeUnload_time = 0
|
||||
window.onbeforeunload = () => {
|
||||
_beforeUnload_time = new Date().getTime()
|
||||
}
|
||||
window.onunload = () => {
|
||||
const _gap_time = new Date().getTime() - _beforeUnload_time
|
||||
if (_gap_time <= 5) {
|
||||
this.send({
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
action: "left"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send(message) {
|
||||
if (this.ws) {
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message)
|
||||
this.ws.send(messageStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.webSocketPubSub = new WebSocketPubSub()
|
||||
</script>
|
||||
|
||||
<!--vuex-->
|
||||
<script type="text/javascript">
|
||||
console.log(${json(@auth.getPrincipalProperty('birthday'))})
|
||||
|
||||
window.sessionStorage.setItem("user",JSON.stringify(${json(@auth.getLogonUser())}))
|
||||
window.store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: "zhgh-vuex",
|
||||
paths: ["openedMenus", "activeMenuIndex", "activeMenuModuleId"],
|
||||
storage: window.localStorage
|
||||
})
|
||||
],
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user")),
|
||||
//当前激活的菜单
|
||||
activeMenuIndex: null,
|
||||
//打开菜单的数组
|
||||
openedMenus: [],
|
||||
//激活的菜单模块
|
||||
activeMenuModuleId: null,
|
||||
//点击过的菜单(历史标签页使用)
|
||||
historyMenuIds: [],
|
||||
//消息列表
|
||||
messages: [],
|
||||
//聊天室
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
//签字
|
||||
signature: {}
|
||||
},
|
||||
getters: {
|
||||
fullMenus: (state) => {
|
||||
return state.user.menus
|
||||
},
|
||||
pcModules: (state) => {
|
||||
return state.user.pcModuleMenus
|
||||
},
|
||||
historyMenuIds: (state) => {
|
||||
return state.historyMenuIds
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
//设置用户信息
|
||||
setUser(state, payload) {
|
||||
state.user = payload
|
||||
},
|
||||
//设置激活的菜单
|
||||
setActiveMenuIndex(state, payload) {
|
||||
state.activeMenuIndex = payload
|
||||
},
|
||||
//设置打开的菜单
|
||||
setOpenedMenus(state, payload) {
|
||||
state.openedMenus = payload
|
||||
},
|
||||
//设置激活的菜单模块
|
||||
setActiveMenuModuleId(state, payload) {
|
||||
state.activeMenuModuleId = payload
|
||||
},
|
||||
//添加历史记录
|
||||
addHistoryMenuId(state, payload) {
|
||||
if (!payload) {
|
||||
return
|
||||
}
|
||||
const exists = state.historyMenuIds.includes(payload)
|
||||
if (!exists) {
|
||||
state.historyMenuIds.push(payload)
|
||||
}
|
||||
},
|
||||
//移除某条历史记录
|
||||
removeHistoryMenuId(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => id !== payload)
|
||||
},
|
||||
//移除多条历史记录
|
||||
removeHistoryMenuIds(state, payload) {
|
||||
state.historyMenuIds = state.historyMenuIds.filter((id) => !payload.includes(id))
|
||||
},
|
||||
//清空历史记录
|
||||
clearHistoryMenuIds(state) {
|
||||
state.historyMenuIds = []
|
||||
state.activeMenuIndex = null
|
||||
state.openedMenus = []
|
||||
pjaxReplace("/platform/home")
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
pjaxRoute({ commit, dispatch, state, getters }, href) {
|
||||
const { pathname, search } = new URL(href, window.location.origin)
|
||||
const matchMenu = getters.fullMenus.find((menu) => menu.href === pathname)
|
||||
if (!matchMenu) {
|
||||
commit("setActiveMenuIndex", null)
|
||||
commit("setOpenedMenus", [])
|
||||
if (!state.activeModuleId && getters.pcModules) {
|
||||
commit("setActiveMenuModuleId", getters.pcModules[0].id)
|
||||
}
|
||||
pjaxReplace(pathname === "/platform/home" ? pathname : pathname)
|
||||
return
|
||||
}
|
||||
//拷贝一份
|
||||
let cloneMenu = JSON.parse(JSON.stringify(matchMenu))
|
||||
//找出所有父菜单包括自己
|
||||
let parentMenus = []
|
||||
while (cloneMenu && cloneMenu.parentId) {
|
||||
cloneMenu = getters.fullMenus.find((m) => m.id === cloneMenu.parentId)
|
||||
if (cloneMenu) {
|
||||
parentMenus.unshift(cloneMenu)
|
||||
}
|
||||
}
|
||||
parentMenus.push(matchMenu)
|
||||
|
||||
//获取到该菜单所属的模块 从一级菜单获取
|
||||
const moduleId = parentMenus[0]?.moduleId
|
||||
//判断下是否跨模块跳转
|
||||
if (moduleId !== state.activeMenuModuleId) {
|
||||
commit("setActiveMenuModuleId", moduleId)
|
||||
}
|
||||
Vue.nextTick(()=>{
|
||||
//重新设置展开菜单
|
||||
commit("setOpenedMenus", parentMenus.map((menu) => menu.id))
|
||||
//设置当前激活菜单
|
||||
commit("setActiveMenuIndex", matchMenu.id)
|
||||
//判断是否加入到历史点击菜单
|
||||
commit("addHistoryMenuId", matchMenu.id)
|
||||
//跳转
|
||||
pjaxReplace(matchMenu.href ? matchMenu.href + (search || "") : "/platform/home")
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--beetl全局变量方法-->
|
||||
<script type="text/javascript">
|
||||
const base = "${base!}"
|
||||
const APP_DOMAIN = "${AppDomain!}"
|
||||
</script>
|
||||
|
||||
<!--vue挂载-->
|
||||
<script type="text/javascript">
|
||||
Vue.prototype.$moment = moment
|
||||
Vue.prototype.$businessTool = businessTool
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
Vue.prototype.$downLoad = commonUtil.downLoadService
|
||||
</script>
|
||||
|
||||
<!--vue全局组件-->
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
Vue.component("guava", httpVueLoader("/components/plugins/Guava.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-select", httpVueLoader("/components/plugins/sysDict/DictSelect.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-upload", httpVueLoader("/components/plugins/sysUpload/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-select", httpVueLoader("/components/plugins/sysUserSelect/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("table-tool", httpVueLoader("/components/plugins/sysTableTool/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("signature", httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("pc-signature", httpVueLoader("/components/plugins/sysSignature/pc.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search", httpVueLoader("/components/plugins/sysPageFormSearch/search.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-item", httpVueLoader("/components/plugins/sysPageFormSearch/searchItem.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-query", httpVueLoader("/components/plugins/sysPageFormSearch/searchQuery.vue?v=" + new Date().getTime()))
|
||||
Vue.component("el-spin", httpVueLoader("/components/plugins/spin/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-opinion-textarea", httpVueLoader("/components/plugins/sysUserAuditOpinion/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-audit", httpVueLoader("/components/plugins/flow/audit/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("icon-selector", httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="ele-admin-layout ele-admin-fixed-body ele-admin-side-dark ele-admin-show-tabs ele-admin-responsive">
|
||||
<div class="ele-admin-header" id="ele-admin-header" v-cloak>
|
||||
<div class="ele-admin-logo">
|
||||
<img src="${AppLogo!}" alt="logo" @click="$store.dispatch('pjaxRoute','/platform/home')" />
|
||||
</div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<div class="ele-admin-header-tool-item collapse-menu" style="color: var(--color-white)" @click="collapseMenu">
|
||||
<i class="el-icon-s-unfold"></i>
|
||||
</div>
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<!--菜单模块-->
|
||||
<header-menu-module></header-menu-module>
|
||||
</div>
|
||||
</div>
|
||||
<!--面包屑-->
|
||||
<div class="ele-admin-breadcrumb">
|
||||
<header-bread-crumb></header-bread-crumb>
|
||||
</div>
|
||||
<div class="ele-admin-header-nav"></div>
|
||||
<div class="ele-admin-header-tool">
|
||||
<!--消息通知-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-message></header-message>
|
||||
</div>
|
||||
<!--个人中心/退出-->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-user-avatar></header-user-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-admin-main">
|
||||
<div class="ele-admin-sidebar" id="ele-admin-sidebar" v-cloak>
|
||||
<sidebar-left-menu></sidebar-left-menu>
|
||||
</div>
|
||||
<div class="ele-admin-body">
|
||||
<div id="ele-admin-tabs">
|
||||
<header-tab-page></header-tab-page>
|
||||
</div>
|
||||
<div class="ele-admin-content">
|
||||
<div class="ele-admin-content-view">
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ele-admin-shade"
|
||||
onclick="document.querySelector('body >div > div.ele-admin-layout').classList.add('ele-admin-collapse')"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
<!--左侧菜单 && 头部 && 标签页-->
|
||||
new Vue({
|
||||
el: "#ele-admin-sidebar",
|
||||
components: {
|
||||
"sidebar-left-menu": httpVueLoader("/components/layout/SideBarLeftMenu.vue?v=" + new Date().getTime())
|
||||
}
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-header",
|
||||
store,
|
||||
components: {
|
||||
"header-message": httpVueLoader("/components/layout/HeaderMessage.vue?v=" + new Date().getTime()),
|
||||
"header-bread-crumb": httpVueLoader("/components/layout/HeaderBreadCrumb.vue?v=" + new Date().getTime()),
|
||||
"header-menu-module": httpVueLoader("/components/layout/HeaderMenuModule.vue?v=" + new Date().getTime()),
|
||||
"header-user-avatar": httpVueLoader("/components/layout/HeaderUserAvatar.vue?v=" + new Date().getTime())
|
||||
},
|
||||
methods: {
|
||||
collapseMenu() {
|
||||
document.querySelector("body >div > div.ele-admin-layout").classList.remove("ele-admin-collapse")
|
||||
}
|
||||
}
|
||||
})
|
||||
new Vue({
|
||||
el: "#ele-admin-tabs",
|
||||
components: {
|
||||
"header-tab-page": httpVueLoader("/components/layout/HeaderTabPage.vue?v=" + new Date().getTime())
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,630 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.apps-hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/*border-radius: 8px 8px 0 0;*/
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.apps-hero img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.apps-container {
|
||||
display: flex;
|
||||
/*height: calc(100vh - 64px - 48px - 180px);*/
|
||||
background-color: #fff;
|
||||
/*border-radius: 0 0 8px 8px;*/
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.apps-sidebar {
|
||||
width: 220px;
|
||||
background-color: #f7f7f7;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.apps-sidebar-item {
|
||||
padding: 16px 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.apps-sidebar-item.active {
|
||||
background-color: #e6f7ff;
|
||||
color: var(--color-primary);
|
||||
border-right: 2px solid #1890ff;
|
||||
}
|
||||
|
||||
.apps-sidebar-item:hover:not(.active) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.apps-content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.apps-header {
|
||||
padding: 0 0 20px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.apps-search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.apps-search {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.apps-search input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.apps-search .search-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.apps-filter {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.apps-filter-title {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.apps-filter-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.filter-tag {
|
||||
padding: 5px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-tag.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.filter-tag:not(.active) {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-tag:not(.active):hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.apps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
grid-auto-rows: min-content;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.app-card {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 15px;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.app-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.app-icon i {
|
||||
color: var(--color-primary);
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.favorite-icon {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 18px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.favorite-icon.active {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.favorite-icon:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.app-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.alphabet-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.alphabet-item {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.alphabet-item.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alphabet-item:not(.active) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.alphabet-item:hover:not(.active) {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.app-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ff9800;
|
||||
color: #fff;
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
border-bottom-left-radius: 8px;
|
||||
}
|
||||
|
||||
.apps-wrapper {
|
||||
/*margin: 0 auto;*/
|
||||
/*margin: 24px;*/
|
||||
/*padding: 24px;*/
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.no-results-content {
|
||||
text-align: center;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.no-results i {
|
||||
color: #c0c4cc;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.no-results .title {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-results .hint {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.page-item.disabled {
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-item.disabled:hover {
|
||||
border-color: #d9d9d9;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="apps-wrapper" id="app">
|
||||
<div class="apps-hero">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i8hmpaq7wmewu9c7w3pt9qhu3gb8bkcvow0" alt="应用中心" />
|
||||
</div>
|
||||
|
||||
<div class="apps-container">
|
||||
<!-- Left Sidebar -->
|
||||
<div class="apps-sidebar">
|
||||
<div
|
||||
v-for="category in allCategories"
|
||||
:key="category.id"
|
||||
:class="['apps-sidebar-item', activeCategory === category.id ? 'active' : '']"
|
||||
@click="changeCategory(category.id)"
|
||||
>
|
||||
<i :class="category.icon"></i>
|
||||
{{ category.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="apps-content">
|
||||
<div class="apps-header">
|
||||
<div class="apps-search-container">
|
||||
<div class="apps-search">
|
||||
<input type="text" v-model="searchKeyword" @keyup.enter="search" placeholder="请输入内容" />
|
||||
<i class="el-icon-search search-icon" @click="search"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 字母过滤 -->
|
||||
<div class="apps-filter">
|
||||
<div class="apps-filter-title">首字母:</div>
|
||||
<div class="apps-filter-tags alphabet-filter">
|
||||
<div :class="['filter-tag', activeAlphabet === '' ? 'active' : '']" @click="changeAlphabet('')">全部</div>
|
||||
<div
|
||||
v-for="letter in alphabets"
|
||||
:key="letter"
|
||||
:class="['alphabet-item', activeAlphabet === letter ? 'active' : '']"
|
||||
@click="changeAlphabet(letter)"
|
||||
>
|
||||
{{ letter }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用网格 -->
|
||||
<div class="apps-grid" v-if="applications.length > 0">
|
||||
<div v-for="app in applications" :key="app.id" class="app-card" @click.stop="openApp(app)">
|
||||
<div :class="['app-icon', app.iconType]">
|
||||
<i v-if="app.icon && !app.picIcon" :class="app.icon"></i>
|
||||
<img v-else-if="app.picIcon" :src="app.picIcon" :alt="app.name || ''" />
|
||||
<i v-else class="fa fa-skype"></i>
|
||||
</div>
|
||||
<div class="app-info">
|
||||
<div class="app-title">{{ app.name }}</div>
|
||||
<div class="app-desc" v-if="app.department">{{ app.department }}</div>
|
||||
</div>
|
||||
<div class="app-status" v-if="app.status">{{ app.status }}</div>
|
||||
<div
|
||||
:class="['favorite-icon', app.isFavorite ? 'active' : '']"
|
||||
@click.stop="toggleFavorite(app.id)"
|
||||
:title="app.isFavorite ? '取消收藏' : '收藏'"
|
||||
>
|
||||
<i :class="app.isFavorite ? 'el-icon-star-on' : 'el-icon-star-off'"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div class="loading-container" v-if="loading">
|
||||
<i class="el-icon-loading"></i>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<!-- 无搜索结果 -->
|
||||
<div class="no-results" v-else-if="applications.length === 0">
|
||||
<div class="no-results-content">
|
||||
<i class="el-icon-document" style="font-size: 48px"></i>
|
||||
<p class="title">没有找到匹配的应用</p>
|
||||
<p class="hint">请尝试不同的搜索词或筛选条件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data: {
|
||||
// 应用列表
|
||||
applications: [],
|
||||
// 分类列表
|
||||
categories: [
|
||||
{ id: "all", name: "全部应用", icon: "fa fa-th-large" },
|
||||
{ id: "favorites", name: "我的收藏", icon: "fa fa-star" },
|
||||
{ id: "recommended", name: "推荐应用", icon: "fa fa-thumbs-up" }
|
||||
],
|
||||
// 动态加载的分类
|
||||
dynamicCategories: [],
|
||||
// 当前选中的分类
|
||||
activeCategory: "all",
|
||||
// 字母表筛选
|
||||
alphabets: [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
],
|
||||
activeAlphabet: "",
|
||||
// 搜索关键词
|
||||
searchKeyword: "",
|
||||
// 分页相关
|
||||
currentPage: 1,
|
||||
pageSize: 15,
|
||||
totalPage: 1,
|
||||
total: 0,
|
||||
// 加载状态
|
||||
loading: true
|
||||
},
|
||||
computed: {
|
||||
// 所有分类(固定分类 + 动态分类)
|
||||
allCategories() {
|
||||
return [...this.categories, ...this.dynamicCategories]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时获取分类和应用数据
|
||||
this.loadCategories()
|
||||
this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
// 加载分类数据
|
||||
loadCategories() {
|
||||
$.get("/platform/v4/apps/categories")
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.dynamicCategories = result.data || []
|
||||
} else {
|
||||
console.error("获取应用分类失败:", result.msg)
|
||||
}
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取应用分类异常:", error)
|
||||
})
|
||||
},
|
||||
|
||||
// 加载应用数据
|
||||
loadApps() {
|
||||
this.loading = true
|
||||
|
||||
// 构建请求参数
|
||||
const params = {
|
||||
categoryId:
|
||||
this.activeCategory === "all"
|
||||
? ""
|
||||
: this.activeCategory === "favorites" || this.activeCategory === "recommended"
|
||||
? this.activeCategory
|
||||
: this.activeCategory,
|
||||
letter: this.activeAlphabet,
|
||||
keyword: this.searchKeyword
|
||||
}
|
||||
|
||||
// 将参数转换为URL查询参数
|
||||
const queryString = Object.keys(params)
|
||||
.filter((key) => params[key] !== null && params[key] !== undefined && params[key] !== "")
|
||||
.map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key]))
|
||||
.join("&")
|
||||
|
||||
// 如果是"我的收藏"分类
|
||||
if (this.activeCategory === "favorites") {
|
||||
this.loadFavorites()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是"推荐应用"分类
|
||||
if (this.activeCategory === "recommended") {
|
||||
$.get("/platform/v4/apps/recommended")
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data.list || []
|
||||
} else {
|
||||
console.error("获取推荐应用失败:", result.msg)
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取推荐应用异常:", error)
|
||||
this.loading = false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 发送请求获取普通应用列表
|
||||
$.get("/platform/v4/apps/list?" + queryString)
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
} else {
|
||||
console.error("获取应用列表失败:", result.msg)
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取应用列表异常:", error)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 加载收藏的应用
|
||||
loadFavorites() {
|
||||
$.get("/platform/v4/apps/favorite")
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
} else {
|
||||
console.error("获取收藏应用失败:", result.msg)
|
||||
}
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error("获取收藏应用异常:", error)
|
||||
})
|
||||
},
|
||||
|
||||
// 切换应用收藏状态
|
||||
toggleFavorite(appId) {
|
||||
const app = this.applications.find((a) => a.id === appId)
|
||||
if (!app) return
|
||||
|
||||
const url = app.isFavorite ? "/platform/v4/apps/removeFavorite" : "/platform/v4/apps/addFavorite"
|
||||
const params = { appId: appId }
|
||||
|
||||
$.post(url, params)
|
||||
.then((result) => {
|
||||
if (result.code === 0) {
|
||||
app.isFavorite = !app.isFavorite
|
||||
this.$message.success(app.isFavorite ? "收藏成功" : "取消收藏成功")
|
||||
} else {
|
||||
console.error(app.isFavorite ? "取消收藏失败:" : "收藏失败:", result.msg)
|
||||
}
|
||||
})
|
||||
.fail((error) => {
|
||||
console.error(app.isFavorite ? "取消收藏异常:" : "收藏异常:", error)
|
||||
})
|
||||
},
|
||||
|
||||
// 切换应用分类
|
||||
changeCategory(categoryId) {
|
||||
this.activeCategory = categoryId
|
||||
this.currentPage = 1
|
||||
|
||||
// 如果是"我的收藏"分类
|
||||
if (categoryId === "favorites") {
|
||||
this.loadFavorites()
|
||||
} else {
|
||||
this.loadApps()
|
||||
}
|
||||
},
|
||||
|
||||
// 切换应用字母
|
||||
changeAlphabet(letter) {
|
||||
this.activeAlphabet = letter
|
||||
this.currentPage = 1
|
||||
this.loadApps()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.loadApps()
|
||||
},
|
||||
|
||||
// 打开应用
|
||||
openApp(app) {
|
||||
// 储存到缓存
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
|
||||
// 新标签页打开
|
||||
window.open("/platform/v4/subApp?appId=" + app.id, "_blank")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,619 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="${AppName!}" />
|
||||
<title>${AppName!}</title>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css" />
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/nprogress/0.2.0/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
|
||||
<!-- 农历插件 -->
|
||||
<script src="${base!}/assets/platform/plugins/lunar/lunar.js"></script>
|
||||
<!-- 时间日期处理工具类 -->
|
||||
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
|
||||
<!--axios-->
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
|
||||
<!--图片预览-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
|
||||
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
|
||||
<!--签字canvas-->
|
||||
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
|
||||
<!--二维码-->
|
||||
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css" />
|
||||
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
<script src="${base!}/components/plugins/sysIconSelector/iconSelect.js"></script>
|
||||
<!--MinDash-->
|
||||
<script src="${base!}/assets/platform/plugins/min-dash/min-dash.js"></script>
|
||||
<!--Sortable-->
|
||||
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
|
||||
<!--vxe-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-pc-ui.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vxe/vxe-table.css" />
|
||||
|
||||
<script src="https://vxeui.com/umd/xe-utils@3.5.30/dist/xe-utils.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-pc-ui@3.1.25/lib/index.umd.min.js"></script>
|
||||
<script src="https://vxeui.com/umd/vxe-table@3.9.0/lib/index.umd.min.js"></script>
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
|
||||
<!-- <script src="${base!}/assets/platform/plugins/snaker/SnakerflowDesigner.umd.min.js"></script>-->
|
||||
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.config.devtools = false
|
||||
ELEMENT.locale(ELEMENT.lang["${lang,escape}"])
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
})
|
||||
Vue.use(FcDesigner)
|
||||
Vue.use(FcDesigner.formCreate)
|
||||
</script>
|
||||
|
||||
<!--ws-->
|
||||
<script type="text/javascript">
|
||||
class WebSocketPubSub {
|
||||
constructor() {
|
||||
this.ws = null
|
||||
this.subscribers = new Map()
|
||||
this.isSubscribed = false // 添加订阅状态标记
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
this.setupPing()
|
||||
this.setupWindowEvents()
|
||||
} catch (ex) {
|
||||
console.info(ex)
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
const WS_URL = window.location.host + "${base}/websocket"
|
||||
const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"
|
||||
this.ws = new WebSocket(protocol + WS_URL)
|
||||
}
|
||||
|
||||
//订阅
|
||||
subscribe(type, callback) {
|
||||
if (!this.subscribers.has(type)) {
|
||||
this.subscribers.set(type, new Set())
|
||||
}
|
||||
this.subscribers.get(type).add(callback)
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.ws.readyState)
|
||||
// 所有订阅完成后,发送 join 消息
|
||||
if (!this.isSubscribed && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.isSubscribed = true
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
setupEventListeners() {
|
||||
// 消息处理
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
// 触发订阅者的回调
|
||||
if (this.subscribers.has(data.action)) {
|
||||
this.subscribers.get(data.action).forEach((callback) => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error("订阅者回调执行错误:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("消息解析错误:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 连接建立
|
||||
this.ws.onopen = () => {
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// room: store.state.room,
|
||||
// action: "join"
|
||||
// })
|
||||
// )
|
||||
// 连接建立时不立即发送 join,等待订阅完成
|
||||
this.ping()
|
||||
}
|
||||
}
|
||||
|
||||
// 新增发送 join 消息的方法
|
||||
sendJoinMessage() {
|
||||
this.send({
|
||||
room: store.state.room,
|
||||
action: "join"
|
||||
})
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
setupPing() {
|
||||
setInterval(() => this.ping(), (11624317 / 1000) * 2)
|
||||
}
|
||||
|
||||
ping() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send("{}")
|
||||
} catch (e) {
|
||||
this.reconnect()
|
||||
}
|
||||
} else {
|
||||
this.reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close() // 确保旧连接已关闭
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
this.setupEventListeners()
|
||||
// 如果之前已订阅,重新发送join消息
|
||||
if (this.isSubscribed) {
|
||||
setTimeout(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.sendJoinMessage()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}, 3000) // 延迟3秒重连
|
||||
}
|
||||
|
||||
// 窗口事件处理
|
||||
setupWindowEvents() {
|
||||
let _beforeUnload_time = 0
|
||||
window.onbeforeunload = () => {
|
||||
_beforeUnload_time = new Date().getTime()
|
||||
}
|
||||
window.onunload = () => {
|
||||
const _gap_time = new Date().getTime() - _beforeUnload_time
|
||||
if (_gap_time <= 5) {
|
||||
this.send({
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
action: "left"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send(message) {
|
||||
if (this.ws) {
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message)
|
||||
this.ws.send(messageStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.webSocketPubSub = new WebSocketPubSub()
|
||||
</script>
|
||||
|
||||
<!--vuex-->
|
||||
<script type="text/javascript">
|
||||
window.store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: "zhgh-vuex"
|
||||
})
|
||||
],
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user")),
|
||||
//消息列表
|
||||
messages: [],
|
||||
//聊天室
|
||||
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
|
||||
//签字
|
||||
signature: {}
|
||||
},
|
||||
getters: {
|
||||
fullMenus: (state) => {
|
||||
return state.user.menus
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
//设置用户信息
|
||||
setUser(state, payload) {
|
||||
state.user = payload
|
||||
}
|
||||
},
|
||||
actions: {}
|
||||
})
|
||||
|
||||
$.get("/platform/sys/user/getLogonUser").then((res) => {
|
||||
if (res.code === 0) {
|
||||
window.sessionStorage.setItem("user", JSON.stringify(res.data))
|
||||
store.commit("setUser", res.data)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--beetl全局变量方法-->
|
||||
<script type="text/javascript">
|
||||
const base = "${base!}"
|
||||
const APP_DOMAIN = "${AppDomain!}"
|
||||
</script>
|
||||
|
||||
<!--vue挂载-->
|
||||
<script type="text/javascript">
|
||||
Vue.prototype.$moment = moment
|
||||
Vue.prototype.$businessTool = businessTool
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
Vue.prototype.$downLoad = commonUtil.downLoadService
|
||||
</script>
|
||||
|
||||
<!--vue全局组件-->
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
Vue.component("guava", httpVueLoader("/components/plugins/Guava.vue?v=" + new Date().getTime()))
|
||||
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dict-select", httpVueLoader("/components/plugins/sysDict/DictSelect.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-upload", httpVueLoader("/components/plugins/sysUpload/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-select", httpVueLoader("/components/plugins/sysUserSelect/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("table-tool", httpVueLoader("/components/plugins/sysTableTool/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("signature", httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("pc-signature", httpVueLoader("/components/plugins/sysSignature/pc.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search", httpVueLoader("/components/plugins/sysPageFormSearch/search.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-item", httpVueLoader("/components/plugins/sysPageFormSearch/searchItem.vue?v=" + new Date().getTime()))
|
||||
Vue.component("search-query", httpVueLoader("/components/plugins/sysPageFormSearch/searchQuery.vue?v=" + new Date().getTime()))
|
||||
Vue.component("el-spin", httpVueLoader("/components/plugins/spin/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("user-opinion-textarea", httpVueLoader("/components/plugins/sysUserAuditOpinion/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-audit", httpVueLoader("/components/plugins/flow/audit/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("icon-selector", httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
Vue.component("flow-form-button", httpVueLoader("/components/plugins/flowable/formButton.vue?v=" + new Date().getTime()))
|
||||
Vue.component("snaker-flow", httpVueLoader("/components/plugins/snaker/snakerFlow.vue?v=" + new Date().getTime()))
|
||||
Vue.component("snaker-flow-task-form-action", httpVueLoader("/components/plugins/snaker/snakerFlowTaskFormAction.vue?v=" + new Date().getTime()))
|
||||
Vue.component("snaker-flow-history-approval", httpVueLoader("/components/plugins/snaker/snakerFlowHisApproval.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.v4-header {
|
||||
background-color: rgb(0, 109, 185);
|
||||
box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3);
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.v4-logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-logo {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.v4-nav {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-nav-item {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
column-gap: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v4-nav-item:hover,
|
||||
.v4-nav-item.active {
|
||||
color: #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.v4-nav-item.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
height: 100%;
|
||||
max-width: 310px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.v4-notification {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-notification:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-notification i {
|
||||
font-size: 18px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.v4-notification:hover i {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
color: rgb(0, 109, 185);
|
||||
border-radius: 10px;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.v4-user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 12px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-user-info:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(0, 109, 185);
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.v4-user-name {
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v4-logout {
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
padding: 0 14px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v4-logout:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.v4-left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.v4-nav {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.v4-user-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.v4-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="v4-header">
|
||||
<div class="v4-left-section">
|
||||
<div class="v4-logo-container">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i843ts1nwl3w2n5iw31ohn2n10cripk3gw0" alt="Logo" class="v4-logo" />
|
||||
</div>
|
||||
|
||||
<nav class="v4-nav">
|
||||
<a href="/platform/v4/home" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-home"></i>
|
||||
首页
|
||||
</a>
|
||||
<a href="/platform/v4/apps" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
应用中心
|
||||
</a>
|
||||
<a href="/flow/todoCenter" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
待办中心
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="v4-user-section">
|
||||
<div class="v4-user-info">
|
||||
<span class="v4-user-name">${@auth.getPrincipalProperty('username')} (${@auth.getPrincipalProperty('loginname')})</span>
|
||||
<!-- <i class="fa fa-angle-down"></i> -->
|
||||
</div>
|
||||
<div class="v4-logout">
|
||||
<i class="fa fa-sign-out"></i>
|
||||
退出
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="v4-content">
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// 处理导航项的active状态
|
||||
$(document).ready(function () {
|
||||
// 初始化:根据当前URL设置active状态
|
||||
function setActiveNavItem() {
|
||||
var currentPath = window.location.pathname
|
||||
$(".v4-nav-item").removeClass("active")
|
||||
$(".v4-nav-item").each(function () {
|
||||
if ($(this).attr("href") === currentPath) {
|
||||
$(this).addClass("active")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 页面加载时设置active状态
|
||||
setActiveNavItem()
|
||||
|
||||
// 为导航项添加点击事件
|
||||
$(".v4-nav-item").on("click", function () {
|
||||
$(".v4-nav-item").removeClass("active")
|
||||
$(this).addClass("active")
|
||||
})
|
||||
|
||||
$(document).pjax(".v4-nav a[data-pjax]", "#container", {
|
||||
maxCacheLength: 0,
|
||||
push: false,
|
||||
replace: true,
|
||||
fragment: "#container",
|
||||
timeout: 8000
|
||||
})
|
||||
|
||||
// 使用pjax的事件来处理导航切换
|
||||
$(document).on("pjax:complete", function () {
|
||||
setActiveNavItem()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,310 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/fonts/element-icons.woff" />
|
||||
|
||||
<!-- import Vue before Element -->
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vuex-persistedstate/vuex-persistedstate.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
|
||||
|
||||
<!-- import ElementUI -->
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
|
||||
|
||||
<!-- import Jquery -->
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.v4-header {
|
||||
background-color: rgb(0, 109, 185);
|
||||
box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3);
|
||||
padding: 0 24px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.v4-logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-logo {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.v4-nav {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-nav-item {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.v4-nav-item:hover,
|
||||
.v4-nav-item.active {
|
||||
color: #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.v4-nav-item.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v4-notification {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-notification:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-notification i {
|
||||
font-size: 18px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.v4-notification:hover i {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
color: rgb(0, 109, 185);
|
||||
border-radius: 10px;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.v4-user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 12px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.v4-user-info:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(0, 109, 185);
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.v4-user-name {
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-logout {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
cursor: pointer;
|
||||
padding: 0 14px;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.v4-logout:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-content {
|
||||
/* padding: 24px; */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.v4-left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.v4-header {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.v4-nav {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.v4-user-section {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.v4-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v4-user-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="v4-header">
|
||||
<div class="v4-left-section">
|
||||
<div class="v4-logo-container">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i843ts1nwl3w2n5iw31ohn2n10cripk3gw0" alt="Logo" class="v4-logo" />
|
||||
</div>
|
||||
|
||||
<nav class="v4-nav">
|
||||
<a href="/platform/v4/home" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-home"></i>
|
||||
首页
|
||||
</a>
|
||||
<a href="/platform/v4/apps" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
应用中心
|
||||
</a>
|
||||
<a href="/platform/v4/apps" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-comments"></i>
|
||||
咨询中心
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="v4-user-section">
|
||||
<div class="v4-user-info">
|
||||
<div class="v4-avatar">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<span class="v4-user-name">用户名</span>
|
||||
<i class="fa fa-angle-down"></i>
|
||||
</div>
|
||||
<div class="v4-logout">
|
||||
<i class="fa fa-sign-out"></i>
|
||||
退出
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="v4-content">
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="ele-body" id="container">${layoutContent}</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// 处理导航项的active状态
|
||||
$(document).ready(function() {
|
||||
// 初始化:根据当前URL设置active状态
|
||||
function setActiveNavItem() {
|
||||
var currentPath = window.location.pathname;
|
||||
$('.v4-nav-item').removeClass('active');
|
||||
$('.v4-nav-item').each(function() {
|
||||
if ($(this).attr('href') === currentPath) {
|
||||
$(this).addClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时设置active状态
|
||||
setActiveNavItem();
|
||||
|
||||
// 使用pjax的事件来处理导航切换
|
||||
$(document).on('pjax:complete', function() {
|
||||
setActiveNavItem();
|
||||
});
|
||||
|
||||
// 为导航项添加点击事件
|
||||
$('.v4-nav-item').on('click', function() {
|
||||
$('.v4-nav-item').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.sub-app-container {
|
||||
/*max-width: 80%;*/
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
/*border-radius: 8px;*/
|
||||
/*box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);*/
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
min-height: calc(100vh - 64px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
width: 250px;
|
||||
background: rgb(64 109 157);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-link {
|
||||
padding: 12px 20px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 3px solid transparent;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-link > span {
|
||||
white-space: nowrap; /* 禁止换行 */
|
||||
overflow: hidden; /* 隐藏超出部分 */
|
||||
text-overflow: ellipsis; /* 显示省略号 */
|
||||
flex: 1; /* 让文字部分占据剩余空间 */
|
||||
}
|
||||
|
||||
.menu-link:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-left-color: #fff;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.menu-link.active {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-left-color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.submenu.active {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.submenu .menu-link {
|
||||
padding-left: 50px;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.submenu .menu-link:hover {
|
||||
padding-left: 55px;
|
||||
}
|
||||
|
||||
.submenu .submenu .menu-link {
|
||||
padding-left: 70px;
|
||||
}
|
||||
|
||||
.submenu .submenu .menu-link:hover {
|
||||
padding-left: 75px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.menu-toggle.active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content-subtitle {
|
||||
color: #7f8c8d;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sub-app-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sub-app-container" id="sub-app-container">
|
||||
<!-- 左侧菜单区域 -->
|
||||
<nav class="sidebar-menu">
|
||||
<div class="menu-header">
|
||||
<h2 class="menu-title">福利管理系统</h2>
|
||||
</div>
|
||||
|
||||
<!--# if(!isEmpty(menus)) { #-->
|
||||
<ul class="menu-list">
|
||||
<!--# for(menu in menus){ #-->
|
||||
<li class="menu-item" data-id="${menu.id}">
|
||||
<!--# if(!isEmpty(menu.children)) { #-->
|
||||
<a href="${menu.href!}" class="menu-link">
|
||||
<!--# if(!isEmpty(menu.icon)) { #-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } else {#-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } #-->
|
||||
<span>${menu.name}</span>
|
||||
<i class="menu-toggle fa fa-chevron-down"></i>
|
||||
</a>
|
||||
<ul class="submenu">
|
||||
<!--# for(child in menu.children){ #-->
|
||||
<li class="menu-item" data-id="${child.id}">
|
||||
<a href="${child.href!}" data-pjax class="menu-link">
|
||||
<!--# if(!isEmpty(child.icon)) { #-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } else {#-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } #-->
|
||||
<span>${child.name}</span>
|
||||
</a>
|
||||
</li>
|
||||
<!--# } #-->
|
||||
</ul>
|
||||
<!--# } else { #-->
|
||||
<a href="${menu.href!}" data-pjax class="menu-link">
|
||||
<!--# if(!isEmpty(menu.icon)) { #-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } else {#-->
|
||||
<i class="fa fa-file menu-icon"></i>
|
||||
<!--# } #-->
|
||||
<span>${menu.name}</span>
|
||||
</a>
|
||||
<!--# } #-->
|
||||
</li>
|
||||
<!--# } #-->
|
||||
</ul>
|
||||
<!--# } #-->
|
||||
</nav>
|
||||
|
||||
<!-- 右侧主体区域 -->
|
||||
<main class="main-content">
|
||||
<header class="content-header">
|
||||
<h1 class="content-title">福利项目管理</h1>
|
||||
<!-- <p class="content-subtitle"></p>-->
|
||||
</header>
|
||||
|
||||
<div class="content-body" id="sub-app-container-main-content">${layoutContent!}</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const menuLinks = document.querySelectorAll(".menu-link")
|
||||
const contentTitle = document.querySelector(".content-title")
|
||||
menuLinks.forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
// 检查是否有 data-pjax 属性
|
||||
const pjax = this.hasAttribute("data-pjax")
|
||||
|
||||
if (pjax) {
|
||||
// 如果有 pjax 属性,不阻止默认行为,让 pjax 处理
|
||||
// 但仍然处理菜单样式更新
|
||||
} else {
|
||||
// 如果没有 pjax 属性,阻止默认跳转
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// 移除所有active状态
|
||||
menuLinks.forEach((l) => l.classList.remove("active"))
|
||||
|
||||
if (!pjax) {
|
||||
// 子目录
|
||||
const submenu = this.nextElementSibling
|
||||
// 子目录有没有展开
|
||||
const isActive = submenu && submenu.classList.contains("active")
|
||||
|
||||
// 关闭所有子菜单
|
||||
document.querySelectorAll(".submenu").forEach((sm) => {
|
||||
sm.classList.remove("active")
|
||||
})
|
||||
document.querySelectorAll(".menu-toggle").forEach((mt) => {
|
||||
mt.classList.remove("active")
|
||||
})
|
||||
|
||||
// 切换当前子菜单状态
|
||||
if (submenu && !isActive) {
|
||||
submenu.classList.add("active")
|
||||
const menuToggle = this.querySelector(".menu-toggle")
|
||||
if (menuToggle) {
|
||||
// 给 menu-toggle 增加 class
|
||||
menuToggle.classList.add("active")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 业务菜单
|
||||
// 更新主内容区域标题
|
||||
// const menuText = this.textContent.trim()
|
||||
// contentTitle.textContent = menuText
|
||||
}
|
||||
|
||||
// 添加当前点击的active状态
|
||||
this.classList.add("active")
|
||||
})
|
||||
})
|
||||
|
||||
$(document).pjax("#sub-app-container .sidebar-menu a[data-pjax]", "#sub-app-container-main-content", {
|
||||
maxCacheLength: 0,
|
||||
push: true,
|
||||
replace: false,
|
||||
fragment: "#sub-app-container-main-content",
|
||||
timeout: 8000
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="approvalApp" v-cloak>
|
||||
<snaker-flow
|
||||
:task_id="taskId"
|
||||
:instance_id="instanceId"
|
||||
:business_id="businessId"
|
||||
:pjax_config="{
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
}"
|
||||
></snaker-flow>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#approvalApp",
|
||||
data() {
|
||||
return {
|
||||
taskId: null,
|
||||
instanceId: null,
|
||||
businessId: null,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskSubmitted() {},
|
||||
handleCancel() {
|
||||
window.history.back()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskId = new URLSearchParams(window.location.search).get("taskId")
|
||||
this.instanceId = new URLSearchParams(window.location.search).get("instanceId")
|
||||
this.businessId = new URLSearchParams(window.location.search).get("businessId")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,49 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="flowCommonApprovalForm" v-cloak>
|
||||
<snaker-flow
|
||||
:task_id="taskId"
|
||||
:instance_id="instanceId"
|
||||
:business_id="businessId"
|
||||
:pjax_urls="{
|
||||
applicationInfo: '/platform/article/write/index_',
|
||||
taskForm: '/platform/article/write/index_'
|
||||
}"
|
||||
:pjax_config="{
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
}"
|
||||
></snaker-flow>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#flowCommonApprovalForm",
|
||||
data() {
|
||||
return {
|
||||
taskId: null,
|
||||
instanceId: null,
|
||||
businessId: null,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskSubmitted() {},
|
||||
handleCancel() {
|
||||
window.history.back()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskId = new URLSearchParams(window.location.search).get("taskId")
|
||||
this.instanceId = new URLSearchParams(window.location.search).get("instanceId")
|
||||
this.businessId = new URLSearchParams(window.location.search).get("businessId")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,147 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.displayName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.name" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类">
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id ===
|
||||
row.category)?.categoryName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="state" label="状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.state===1" type="success">启用</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.state===0" type="info">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="350px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button type="primary" size="mini" @click="onView(row)">查看</el-button>
|
||||
<el-button v-if="row.state===1" type="danger" size="mini" @click="onDisable(row)">停用
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="primary" size="mini" @click="onEnable(row)">启用
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="danger" size="mini" @click="onDelete(row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog title="查看流程图" :visible.sync="designVisible" class="design-dialog" width="80%">
|
||||
<div style="height: calc(100vh - 150px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%"
|
||||
style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onView(row) {
|
||||
this.designVisible = true
|
||||
this.designUrl = "/flow/define/preview?id=" + row.id
|
||||
},
|
||||
|
||||
onDisable(row) {
|
||||
this.$confirm("确定要停用该流程吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/define/disable", { id: row.id }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("停用成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onEnable(row) {
|
||||
this.$confirm("确定要启用该流程吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/define/enable", { id: row.id }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("启用成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/define/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.categoryOptions = res.data
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>流程设计器</title>
|
||||
<script src="/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
<!-- 引入 core 包和对应 css-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/logic-flow.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/style/index.css" />
|
||||
<script src="/assets/platform/plugins/snaker/SnakerflowDesigner.umd.js"></script>
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<snaker-flow-designer ref="designer"
|
||||
v-model="flowData"
|
||||
:show-doc="false"
|
||||
:viewer="true"
|
||||
node-render-type="html"
|
||||
:wf-config="{
|
||||
showHelp: false,
|
||||
}"
|
||||
:config="{
|
||||
grid:{ size: 25,visible: true,type: 'dot',config:{color: '#ababab',thickness: 1}}
|
||||
}"
|
||||
></snaker-flow-designer>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
Vue.use(SnakerflowDesigner.default)
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
id: "${id!}",
|
||||
flowData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDetail() {
|
||||
$.get("/flow/define/detail", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.flowData = res.data.content
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,185 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>流程设计</title>
|
||||
<link rel="stylesheet" href="/assets/platform/plugins/snaker/snaker.css" type="text/css" media="all" />
|
||||
</head>
|
||||
<body class="pear-container">
|
||||
<div id="toolbox">
|
||||
<div id="toolbox_handle">工具集</div>
|
||||
<div class="node" id="save">
|
||||
<img src="/assets/platform/plugins/snaker/images/save.gif" />
|
||||
保存
|
||||
</div>
|
||||
<div>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="node selectable" id="pointer">
|
||||
<img src="/assets/platform/plugins/snaker/images/select16.gif" />
|
||||
Select
|
||||
</div>
|
||||
<div class="node selectable" id="path">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/flow_sequence.png" />
|
||||
transition
|
||||
</div>
|
||||
<div>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="node state" id="start" type="start">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/start_event_empty.png" />
|
||||
start
|
||||
</div>
|
||||
<div class="node state" id="end" type="end">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/end_event_terminate.png" />
|
||||
end
|
||||
</div>
|
||||
<div class="node state" id="task" type="task">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/task_empty.png" />
|
||||
task
|
||||
</div>
|
||||
<div class="node state" id="task" type="custom">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/task_empty.png" />
|
||||
custom
|
||||
</div>
|
||||
<div class="node state" id="task" type="subprocess">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/task_empty.png" />
|
||||
subprocess
|
||||
</div>
|
||||
<div class="node state" id="fork" type="decision">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/gateway_exclusive.png" />
|
||||
decision
|
||||
</div>
|
||||
<div class="node state" id="fork" type="fork">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/gateway_parallel.png" />
|
||||
fork
|
||||
</div>
|
||||
<div class="node state" id="join" type="join">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/gateway_parallel.png" />
|
||||
join
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="properties">
|
||||
<div id="properties_handle">属性</div>
|
||||
<table class="properties_all" cellpadding="0" cellspacing="0"></table>
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="snakerflow"></div>
|
||||
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/snaker/raphael-min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery-ui.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/dialog.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.designer.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.model.js" charset="utf-8" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.editors.js" type="text/javascript"></script>
|
||||
<script>
|
||||
const processId = "${processId!}"
|
||||
console.log(processId)
|
||||
|
||||
console.log(new URLSearchParams(window.location.search).get("processId"))
|
||||
|
||||
$.post("/snakerflow/define//process/modelJson", { processId }).then((res) => {
|
||||
console.log(res)
|
||||
if (res.code === 0) {
|
||||
let model = res.data
|
||||
if (model) {
|
||||
model = eval("(" + model + ")")
|
||||
}
|
||||
$("#snakerflow").snakerflow({
|
||||
basePath: "/assets/platform/plugins/snaker/",
|
||||
ctxPath: "/",
|
||||
restore: model,
|
||||
userJson: {},
|
||||
formPath: "forms/",
|
||||
tools: {
|
||||
save: {
|
||||
onclick: function (data) {
|
||||
saveModel(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function saveModel(data) {
|
||||
$.post("/snakerflow/define/saveProcessXml", { model: data, id: processId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
// alert("保存成功")
|
||||
window.parent.postMessage("success")
|
||||
} else {
|
||||
window.parent.postMessage("error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// layui.use(['table', 'form', 'jquery', 'common', 'easyAdmin'], function () {
|
||||
// let easyAdmin = layui.easyAdmin;
|
||||
// id = getQueryString("processId");
|
||||
// easyAdmin.httpGet("/flow/process/modelJson?processId=" + id, function (data) {
|
||||
// var model = "";
|
||||
// if (data) {
|
||||
// model = eval("(" + data + ")");
|
||||
// }
|
||||
// var userJson = {};
|
||||
// easyAdmin.httpGet("/sys/user/getAll", function (result) {
|
||||
// if (result.success) {
|
||||
// userJson = result.data;
|
||||
// }
|
||||
// }, true, false);
|
||||
//
|
||||
// var isSubmitting = false; // 添加一个变量用于标记是否正在提交中
|
||||
//
|
||||
// $('#snakerflow').snakerflow({
|
||||
// basePath: "/assets/platform/plugins/snaker/",
|
||||
// ctxPath: "/",
|
||||
// restore: model,
|
||||
// userJson: userJson,
|
||||
// formPath: "forms/",
|
||||
// tools: {
|
||||
// save: {
|
||||
// onclick: function (data) {
|
||||
// if (isSubmitting) {
|
||||
// return false; // 如果正在提交中,则不执行后续代码
|
||||
// }
|
||||
// isSubmitting = true; // 设置为正在提交中状态
|
||||
// saveModel(data);
|
||||
// isSubmitting = false; // 处理完成后重置提交状态
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
//
|
||||
//
|
||||
// function getQueryString(name) {
|
||||
// var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
|
||||
// var r = window.location.search.substr(1).match(reg);
|
||||
// if (r !== null)
|
||||
// return unescape(r[2]);
|
||||
// return "";
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // window.saveModel = function (data) {
|
||||
// // easyAdmin.http({
|
||||
// // type: 'POST',
|
||||
// // url: "/flow/process/deployXml",
|
||||
// // data: "model=" + data + "&id=" + id,
|
||||
// // async: false,
|
||||
// // globle: false,
|
||||
// // success: function (data) {
|
||||
// // parent.layer.close(parent.layer.getFrameIndex(window.name));//关闭当前页
|
||||
// // parent.layui.table.reload("user-table");
|
||||
// // }
|
||||
// // });
|
||||
// //
|
||||
// // }
|
||||
// })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,235 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.displayName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.name" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类">
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id ===
|
||||
row.category)?.categoryName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="state" label="状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.state===1" type="success">启用</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.state===0" type="info">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="350px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="onDesign(row)">设计</el-button>
|
||||
</el-button>
|
||||
<el-button size="mini" type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<!-- <el-button type="text" size="mini" @click="onView(row)">查看</el-button>-->
|
||||
<el-button v-if="row.state===1" type="danger" size="mini" @click="onUndeploy(row)">停用
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="primary" size="mini" @click="onPublish(row)">发布
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="danger" size="mini" @click="onDelete(row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="flowName" :rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="flowCode" :rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowCode" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category" :rules="{ required: true, message: '请选择流程分类', trigger: 'change' }">
|
||||
<el-select v-model="formData.category" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="designVisible" fullscreen class="design-dialog">
|
||||
<div style="height: calc(100vh - 55px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%"
|
||||
style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
<el-dialog title="xml编辑" :visible.sync="xmlVisible" width="70%">
|
||||
<el-input type="textarea" style="width: 100%;" autosize v-model="formData.model"></el-input>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="xmlVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null,
|
||||
xmlVisible: false,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd(){
|
||||
this.formData = {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onSubmit(){
|
||||
$.post("/snakerflow/define/addProcess", this.formData)
|
||||
},
|
||||
|
||||
|
||||
onDesign(row = null) {
|
||||
this.designVisible = true
|
||||
this.iframeLoaded()
|
||||
this.designUrl = "/flow/define/designer2?processId=" + row?.id
|
||||
},
|
||||
|
||||
onEdit(row) {
|
||||
this.xmlVisible = true
|
||||
this.$axios.post("/snakerflow/define/getXml", { id: row.id }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.formData = {
|
||||
id: row.id,
|
||||
model: res.data
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onSave() {
|
||||
$.post("/snakerflow/define/saveProcessXml", { ...this.formData, xmlHeader: true }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.pageData()
|
||||
this.xmlVisible = false
|
||||
} else {
|
||||
this.$message.error("保存失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
iframeLoaded() {
|
||||
// iframe监听组件内设计器保存事件
|
||||
window.onmessage = (event) => {
|
||||
console.log(event)
|
||||
if (event.data === "success") {
|
||||
this.$message.success("保存成功")
|
||||
this.designVisible = false
|
||||
this.pageData()
|
||||
} else if (event.data === "error") {
|
||||
this.$message.error("保存失败")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onUndeploy(row) {
|
||||
this.$confirm("您确定要停用吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/snakerflow/define/undeploy", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onPublish(row) {
|
||||
this.$confirm("您确定要发布吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/snakerflow/define/publish", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/snakerflow/define/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.categoryOptions = res.data
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,254 @@
|
||||
const assigneeForm = {
|
||||
template: /*language=HTML*/`
|
||||
<el-form-item label="参与者">
|
||||
<div>
|
||||
<!-- 模式选择 -->
|
||||
<div style="margin-bottom: 10px;">
|
||||
<el-radio-group v-model="assigneeMode" @change="handleModeChange" size="small">
|
||||
<el-radio-button label="manual" :disabled="isFirstTaskNode">手动输入</el-radio-button>
|
||||
<el-radio-button label="select" :disabled="isFirstTaskNode">选择输入</el-radio-button>
|
||||
<el-radio-button label="initiator">流程发起人</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 手动输入模式 -->
|
||||
<el-input
|
||||
v-if="assigneeMode === 'manual'"
|
||||
v-model="manualInput"
|
||||
placeholder="请输入参与者,多个参与者用逗号分隔"
|
||||
@change="updateModelValue">
|
||||
</el-input>
|
||||
|
||||
<!-- 选择输入模式 -->
|
||||
<el-input
|
||||
v-if="assigneeMode === 'select'"
|
||||
v-model="assigneeText"
|
||||
readonly
|
||||
style="width: 100%"
|
||||
placeholder="请选择参与者">
|
||||
<el-button
|
||||
slot="append"
|
||||
type="primary"
|
||||
style="color: #fff;background-color: var(--color-primary);border-color: var(--color-primary)"
|
||||
icon="el-icon-setting"
|
||||
@click="openUserDialog">
|
||||
选择
|
||||
</el-button>
|
||||
</el-input>
|
||||
|
||||
<!-- 流程发起人模式 -->
|
||||
<el-input
|
||||
v-if="assigneeMode === 'initiator'"
|
||||
value="initiator"
|
||||
readonly
|
||||
disabled
|
||||
style="width: 100%"
|
||||
placeholder="流程发起人">
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<el-dialog title="选择参与者" :visible.sync="dialogVisible" width="80%" append-to-body>
|
||||
<div style="display: flex;">
|
||||
<!-- 左侧表格 -->
|
||||
<div style="flex: 3; margin-right: 10px; overflow: auto;">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<el-input v-model="pageForm.searchKeyword" placeholder="请输入关键字搜索" style="width: 200px; margin-right: 10px;"></el-input>
|
||||
<el-button type="primary" @click="searchUsers">搜索</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="userTableRef"
|
||||
:data="userList"
|
||||
border
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column type="selection" width="55" :reserve-selection="true"></el-table-column>
|
||||
<el-table-column prop="loginname" label="工号" width="100"></el-table-column>
|
||||
<el-table-column prop="username" label="姓名" width="120"></el-table-column>
|
||||
<el-table-column prop="unitName" label="单位"></el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 10px; text-align: right;">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageForm.pageNumber"
|
||||
:page-size="pageForm.pageSize"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="pageForm.totalCount">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧已选列表 -->
|
||||
<div style="flex: 1; border: 1px solid #EBEEF5; border-radius: 4px; padding: 10px; overflow: auto;" v-if="$refs.userTableRef">
|
||||
<div style="font-weight: bold; margin-bottom: 10px;">已选择的参与者</div>
|
||||
<el-tag
|
||||
v-for="user in $refs.userTableRef.selection"
|
||||
:key="user.id"
|
||||
style="margin: 0 5px 5px 0;">
|
||||
{{ user.username }}
|
||||
</el-tag>
|
||||
<div v-if="$refs.userTableRef.selection.length === 0" style="color: #909399; font-size: 14px;">
|
||||
暂无选择的参与者
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmSelection">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</el-form-item>
|
||||
`,
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
start_node_next_node_ids: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 判断当前节点是否为第一个任务节点
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFirstTaskNode() {
|
||||
return this.start_node_next_node_ids.includes(this.model.name)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
assigneeMode: "manual",
|
||||
manualInput: "",
|
||||
assigneeText: "",
|
||||
dialogVisible: false,
|
||||
userList: [],
|
||||
selectedUsers: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
searchKeyword: "",
|
||||
totalCount: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 回显
|
||||
this.initFromModel()
|
||||
},
|
||||
methods: {
|
||||
initFromModel() {
|
||||
const value = this.model[this.field]
|
||||
this.assigneeText = this.model["assigneeText"] || ""
|
||||
this.assigneeMode = this.model["assigneeMode"] || "manual"
|
||||
|
||||
if(this.assigneeMode === "initiator"){
|
||||
this.assignee = "initiator"
|
||||
this.assigneeText = "流程发起人"
|
||||
}
|
||||
|
||||
if(this.assigneeMode === "manual"){
|
||||
this.manualInput = value || ""
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
handleModeChange() {
|
||||
this.assigneeText = ""
|
||||
this.manualInput = ""
|
||||
this.assignee = ""
|
||||
|
||||
// 如果切换到流程发起人模式,直接设置值
|
||||
if (this.assigneeMode === "initiator") {
|
||||
this.assignee = "{$initiator}"
|
||||
this.assigneeText = "流程发起人"
|
||||
}
|
||||
|
||||
this.updateModelValue()
|
||||
},
|
||||
openUserDialog() {
|
||||
this.dialogVisible = true
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.searchKeyword = ""
|
||||
this.loadSelectUsers()
|
||||
},
|
||||
|
||||
loadSelectUsers() {
|
||||
let userIds = this.model[this.field].split(",")
|
||||
$.get("/flow/design/assigneeEcho", { userIds: JSON.stringify(userIds) }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.selectedUsers = res.data
|
||||
this.loadUsers()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
loadUsers() {
|
||||
$.get("/flow/design/assigneePage", {
|
||||
...this.pageForm
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.userList = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
// 设置已选中的行
|
||||
this.$nextTick(() => {
|
||||
this.setTableSelection()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 设置表格选中数据
|
||||
setTableSelection() {
|
||||
// 如果当前表格中有该用户,选中TA
|
||||
this.userList.forEach(row => {
|
||||
const exist = this.selectedUsers.map(item => item.id).includes(row.id)
|
||||
if (exist) {
|
||||
this.$refs.userTableRef.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
confirmSelection() {
|
||||
const selection = this.$refs.userTableRef.selection
|
||||
this.assignee = selection.map(item => item.id).join(",")
|
||||
this.assigneeText = selection.map(item => item.username).join(",")
|
||||
this.updateModelValue()
|
||||
this.dialogVisible = false
|
||||
},
|
||||
updateModelValue() {
|
||||
// 更新model中的值
|
||||
if (this.assigneeMode === "manual") {
|
||||
this.assignee = this.manualInput
|
||||
this.assigneeText = this.manualInput
|
||||
} else if (this.assigneeMode === "initiator") {
|
||||
this.assignee = "initiator"
|
||||
this.assigneeText = "流程发起人"
|
||||
}
|
||||
// 选择模式的处理在confirmSelection中
|
||||
|
||||
this.$set(this.model, this.field, this.assignee)
|
||||
this.$set(this.model, "assigneeText", this.assigneeText)
|
||||
this.$set(this.model, "assigneeMode", this.assigneeMode)
|
||||
},
|
||||
searchUsers() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.loadUsers()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageForm.pageSize = val
|
||||
this.loadUsers()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageForm.pageNumber = val
|
||||
this.loadUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>流程设计器</title>
|
||||
<script src="/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css" />
|
||||
<!-- 引入 core 包和对应 css-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/logic-flow.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/style/index.css" />
|
||||
<script src="/assets/platform/plugins/snaker/SnakerflowDesigner.umd.js"></script>
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<snaker-flow-designer ref="designer"
|
||||
v-model="flowData"
|
||||
@on-save="handleSave"
|
||||
:show-doc="false"
|
||||
node-render-type="html"
|
||||
:extend-property-keys="[
|
||||
'candidateUsers',
|
||||
'candidateGroups',
|
||||
'candidateHandler',
|
||||
'assigneeText',
|
||||
'assigneeMode'
|
||||
]"
|
||||
>
|
||||
<!-- <template v-slot:form-item-task-form="{model,field}">-->
|
||||
<!-- <el-form-item label="form">-->
|
||||
<!-- -->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<template v-slot:form-item-task-assignee="{model,field}">
|
||||
<assignee-form :model="model" :field="field"
|
||||
:start_node_next_node_ids="startNodeNextNodeIds"></assignee-form>
|
||||
</template>
|
||||
|
||||
<template v-slot:form-item-task-assignment-handler="{model,field}">
|
||||
<el-form-item label="参与者处理类">
|
||||
<el-select v-model="model[field]" style="width: 100%" clearable
|
||||
:disabled="startNodeNextNodeIds.includes(model.name)">
|
||||
<el-option v-for="item in assignmentHandlerClassOptions" :key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
</snaker-flow-designer>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
<!--#include('assigneeForm.js'){}#-->
|
||||
|
||||
Vue.use(SnakerflowDesigner.default)
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
components: {
|
||||
"assignee-form": assigneeForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: "${id!}",
|
||||
designerData: {},
|
||||
flowData: {},
|
||||
assignmentHandlerClassOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
startNodeNextNodeIds() {
|
||||
if (this.$refs.designer && this.$refs.designer.lf) {
|
||||
const graphData = this.$refs.designer.lf.getGraphData()
|
||||
if (graphData) {
|
||||
const { nodes, edges } = graphData
|
||||
const startNode = nodes.find(v => v.type === "snaker:start")
|
||||
if (startNode) {
|
||||
const targetNodeIds = edges.filter(v => v.sourceNodeId === startNode.id).map(v => v.targetNodeId)
|
||||
return targetNodeIds
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSave(val) {
|
||||
const design = {
|
||||
...this.designerData,
|
||||
content: val.json
|
||||
}
|
||||
$.post("/flow/design/updateContent", { design: JSON.stringify(design) }).then(res => {
|
||||
if (res.code === 0) {
|
||||
window.parent.postMessage("success")
|
||||
} else {
|
||||
window.parent.postMessage("error")
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetail() {
|
||||
$.get("/flow/design/detail", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.designerData = res.data
|
||||
if (res.data.content) {
|
||||
this.flowData = res.data.content
|
||||
} else {
|
||||
this.flowData = {}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
init() {
|
||||
$.get("/flow/design/assigmentHandlerClass").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.assignmentHandlerClassOptions = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getData() {
|
||||
console.log(this.$refs.designer)
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,214 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.displayName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.name" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="type" label="流程分类"></el-table-column>
|
||||
<el-table-column prop="isDeployed" label="是否部署">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isDeployed===1" type="success">是</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isDeployed===0" type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="450px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="onDesign(row)">设计</el-button>
|
||||
</el-button>
|
||||
<el-button size="mini" type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<!-- v-if="row.isDeployed===0"-->
|
||||
<el-button type="primary" size="mini" @click="onDeploy(row)">部署
|
||||
</el-button>
|
||||
<el-button type="danger" size="mini" @click="onDelete(row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="displayName"
|
||||
:rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.displayName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="name"
|
||||
:rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.name" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category"
|
||||
:rules="{ required: false, message: '请选择流程分类', trigger: 'change' }"
|
||||
style="width: 100%">
|
||||
<el-select v-model="formData.category" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName"
|
||||
:value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="电脑端发起地址" prop="instanceUrl">
|
||||
<el-input v-model="formData.instanceUrl" placeholder="请输入电脑端发起地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机端发起地址" prop="h5InstanceUrl">
|
||||
<el-input v-model="formData.h5InstanceUrl" placeholder="请输入手机端发起地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="icon">
|
||||
<el-input v-model="formData.icon" placeholder="请输入图标"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="designVisible" fullscreen class="design-dialog">
|
||||
<div style="height: calc(100vh - 55px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%"
|
||||
style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd() {
|
||||
this.formData = {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onEdit(row) {
|
||||
this.dialogVisible = true
|
||||
this.formData = { ...row }
|
||||
},
|
||||
onSubmit() {
|
||||
$.post("/flow/design/" + (this.formData.id ? "update" : "insert"), { design: JSON.stringify(this.formData) }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
this.dialogVisible = false
|
||||
}
|
||||
})
|
||||
},
|
||||
onDesign(row) {
|
||||
this.designVisible = true
|
||||
this.iframeLoaded()
|
||||
this.designUrl = "/flow/design/designer?id=" + row.id
|
||||
},
|
||||
|
||||
iframeLoaded() {
|
||||
// iframe监听组件内设计器保存事件
|
||||
window.onmessage = (event) => {
|
||||
console.log(event)
|
||||
if (event.data === "success") {
|
||||
this.$message.success("保存成功")
|
||||
this.designVisible = false
|
||||
this.pageData()
|
||||
} else if (event.data === "error") {
|
||||
this.$message.error("保存失败")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDeploy(row) {
|
||||
this.$confirm("部署会生成新的流程定义版本,确定部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/design/deploy", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onRedeploy(row) {
|
||||
this.$confirm("重新部署会覆盖最新版流程定义,确定重新部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/design/redeploy", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/design/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.categoryOptions = res.data
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,80 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>流程设计器</title>
|
||||
<script src="/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
<!-- 引入 core 包和对应 css-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/logic-flow.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/style/index.css" />
|
||||
<script src="/assets/platform/plugins/snaker/SnakerflowDesigner.umd.js"></script>
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<snaker-flow-designer ref="designer"
|
||||
v-model="flowData"
|
||||
@on-save="handleSave"
|
||||
:show-doc="false"
|
||||
node-render-type="html"
|
||||
:extendPropertyKeys="[
|
||||
'candidateUsers',
|
||||
'candidateGroups',
|
||||
'candidateHandler',
|
||||
'assigneeText',
|
||||
]"
|
||||
></snaker-flow-designer>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
Vue.use(SnakerflowDesigner.default)
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
id: "${id!}",
|
||||
flowData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSave(val) {
|
||||
console.log(val)
|
||||
const data = {
|
||||
processDesignId: this.id,
|
||||
...val.json
|
||||
}
|
||||
$.post("/flow/design/updateDesign", { json: JSON.stringify(data) }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
window.parent.postMessage("success")
|
||||
} else {
|
||||
window.parent.postMessage("error")
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetail() {
|
||||
$.get("/flow/design/detail", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.flowData = res.data.jsonObject
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -3,8 +3,6 @@ layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<!-- <iframe src="/uflo/designer"></iframe>-->
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<el-row type="flex">
|
||||
<el-button @click="openDesigner" size="medium" type="primary">
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>流程状态</title>
|
||||
<link rel="stylesheet" href="/assets/platform/plugins/snaker/snaker.css" type="text/css" media="all"/>
|
||||
<link rel="stylesheet" href="/assets/platform/plugins/snaker/style.css" type="text/css" media="all"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</div>
|
||||
|
||||
<table class="properties_all" align="center" border="1" cellpadding="0" cellspacing="0" style="margin-top: 0px">
|
||||
<div id="snakerflow"
|
||||
style="border: 1px solid #d2dde2; margin-top:10px; margin-left:10px; margin-bottom:10px; width:98%;">
|
||||
</div>
|
||||
</table>
|
||||
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/snaker/raphael-min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery-ui.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/dialog.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.designer.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.model.js" charset="utf-8" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.editors.js" type="text/javascript"></script>
|
||||
<script>
|
||||
|
||||
const orderId = "${orderId!}"
|
||||
const processId = "${processId!}"
|
||||
|
||||
$(function(){
|
||||
$.post("/snakerflow/define/process/json", { processId, orderId }).then(res => {
|
||||
if (res.code === 0) {
|
||||
display(res.data.process, res.data.state)
|
||||
}
|
||||
})
|
||||
|
||||
function display(process, state) {
|
||||
/** view*/
|
||||
$('#snakerflow').snakerflow($.extend(true, {
|
||||
basePath: "/assets/platform/plugins/snaker/",
|
||||
// ctxPath: easyAdmin.GetAdminServerUrl(),
|
||||
// token: easyAdmin.GetTokenQueryString(),
|
||||
orderId: orderId,
|
||||
restore: eval("(" + process + ")"),
|
||||
editable: false
|
||||
}, eval("(" + state + ")")
|
||||
));
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,604 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="searchForm.category" placeholder="所有流程类型" clearable>
|
||||
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tasks"
|
||||
style="width: 100%"
|
||||
:key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="task-title">{{scope.row.processInstanceName || scope.row.title}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="initiatorName" label="申请人" min-width="120">
|
||||
<template slot-scope="{row}">
|
||||
{{row.variable.initiatorName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">
|
||||
<i class="el-icon-time"></i>
|
||||
{{scope.row.createdAt}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'done'">
|
||||
<i class="el-icon-check"></i>
|
||||
{{scope.row.finishTime}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'started'">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
{{scope.row.createdAt}}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="{row}">
|
||||
{{processStatusMap[row.state]?.text}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
|
||||
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.description" label="任务描述">
|
||||
<div class="description-content">{{currentTask.description}}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
|
||||
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 优化后的分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev,
|
||||
.pagination-container .el-pagination .btn-next {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev:hover,
|
||||
.pagination-container .el-pagination .btn-next:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 3px;
|
||||
font-weight: normal;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__jump {
|
||||
margin-left: 15px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input .el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 任务详情样式 */
|
||||
.task-detail .el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
keyword: "",
|
||||
category: ""
|
||||
},
|
||||
|
||||
// 流程类型选项
|
||||
processTypes: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 分页
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false,
|
||||
|
||||
processStatusMap: {
|
||||
10: { text: "进行中", class: "doing" },
|
||||
20: { text: "已完成", class: "finished" },
|
||||
30: { text: "已撤回", class: "withdraw" },
|
||||
40: { text: "强行终止", class: "interrupt" },
|
||||
45: { text: "已拒绝", class: "reject" },
|
||||
50: { text: "挂起", class: "pending" },
|
||||
99: { text: "已废弃", class: "abandon" }
|
||||
},
|
||||
|
||||
// 任务状态枚举
|
||||
taskStateEnum: {
|
||||
DOING: 10,
|
||||
FINISHED: 20,
|
||||
WITHDRAW: 30,
|
||||
INTERRUPT: 40,
|
||||
PENDING: 50,
|
||||
ABANDON: 99
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async getProcessTypes() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/category")
|
||||
if (res.code === 0) {
|
||||
this.processTypes = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取流程类型失败")
|
||||
console.error("获取流程类型失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
type: this.activeTab,
|
||||
keyword: this.searchForm.keyword,
|
||||
category: this.searchForm.category
|
||||
}
|
||||
|
||||
const res = await $.post("/flow/todoCenter/" + this.activeTab, params)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.total = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.searchForm = {
|
||||
keyword: "",
|
||||
category: ""
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 查看任务详情
|
||||
viewTaskDetail(task) {
|
||||
// this.currentTask = task
|
||||
// this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async handleTask(task) {
|
||||
if (!task) return
|
||||
|
||||
// 如果有表单链接,直接跳转
|
||||
if (task.taskMobileFormUrl) {
|
||||
window.open(task.taskMobileFormUrl, "_blank")
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态对应的类型
|
||||
getStatusType(task) {
|
||||
if (!task.status) return "info"
|
||||
|
||||
switch (task.status) {
|
||||
case "审批中":
|
||||
return "primary"
|
||||
case "已完成":
|
||||
return "success"
|
||||
case "已通过":
|
||||
return "success"
|
||||
case "已拒绝":
|
||||
return "danger"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
},
|
||||
|
||||
// 表格行类名
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -1,10 +1,12 @@
|
||||
const SYS_MENU_BASIC_FORM_COMPONENT = {
|
||||
template: `
|
||||
<el-dialog :title="formData.id ? '编辑菜单' : '新增菜单'" :show-close="false" :visible.sync="visibleDialog" :close-on-click-modal="false" width="50%">
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog :title="formData.id ? '编辑菜单' : '新增菜单'" :show-close="false" :visible.sync="visibleDialog"
|
||||
:close-on-click-modal="false" width="50%">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" size="small" label-width="80px">
|
||||
<el-form-item label="所属模块" prop="moduleId">
|
||||
<el-select v-model="formData.moduleId" clearable placeholder="请选择模块">
|
||||
<el-option v-for="item in moduleOptions" :value="item.id" :key="item.id" :label="item.name"></el-option>
|
||||
<el-option v-for="item in moduleOptions" :value="item.id" :key="item.id"
|
||||
:label="item.name"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="parentId" label="上级菜单" label-width="80px">
|
||||
@@ -39,28 +41,37 @@ const SYS_MENU_BASIC_FORM_COMPONENT = {
|
||||
<el-input maxlength="100" placeholder="URL" v-model="formData.href" auto-complete="off" tabindex="6"
|
||||
type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="icon" label="图标">
|
||||
<el-input maxlength="100"
|
||||
v-if="platform==='PC'"
|
||||
placeholder="图标"
|
||||
v-model="formData.icon"
|
||||
auto-complete="off"
|
||||
tabindex="7"
|
||||
type="text">
|
||||
<template slot="append">
|
||||
<el-button @click="$refs.iconSelector.showIconModal(formData.icon)">图标选择</el-button>
|
||||
<icon-selector ref="iconSelector" v-model="formData.icon"></icon-selector>
|
||||
</template>
|
||||
</el-input>
|
||||
<file-upload
|
||||
v-if="platform==='H5'"
|
||||
:value.sync="formData.icon"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<el-form-item prop="picIcon" label="图片图标">
|
||||
<!-- <template v-if="platform==='PC'">-->
|
||||
<!-- <el-input maxlength="100" placeholder="图标" v-model="formData.icon"></el-input>-->
|
||||
<!-- <i :class="formData.icon" v-if="formData.icon"></i>-->
|
||||
<!-- </template> -->
|
||||
<!-- <el-input maxlength="100" -->
|
||||
<!-- v-if="platform==='PC'"-->
|
||||
<!-- placeholder="图标" -->
|
||||
<!-- v-model="formData.icon" -->
|
||||
<!-- auto-complete="off" -->
|
||||
<!-- tabindex="7"-->
|
||||
<!-- type="text">-->
|
||||
<!-- <template slot="append">-->
|
||||
<!-- <el-button @click="$refs.iconSelector.showIconModal(formData.icon)">图标选择</el-button>-->
|
||||
<!-- <icon-selector ref="iconSelector" v-model="formData.icon"></icon-selector>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<file-upload
|
||||
:value.sync="formData.picIcon"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="图标" prop="icon">
|
||||
<el-input maxlength="100" placeholder="图标" v-model="formData.icon"></el-input>
|
||||
<i :class="formData.icon" v-if="formData.icon"></i>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="disabled" label="启用状态">
|
||||
<el-switch v-model="formData.disabled" active-color="#ff4949" inactive-color="#13ce66"></el-switch>
|
||||
</el-form-item>
|
||||
@@ -83,10 +94,12 @@ const SYS_MENU_BASIC_FORM_COMPONENT = {
|
||||
<el-input v-model="menu.name" placeholder="权限名称"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="13">
|
||||
<el-input v-model="menu.permission" placeholder="权限标识,如 sys.manager.menu.add"></el-input>
|
||||
<el-input v-model="menu.permission"
|
||||
placeholder="权限标识,如 sys.manager.menu.add"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button @click.prevent="formRemoveMenu(menu)" icon="el-icon-delete" size="small"></el-button>
|
||||
<el-button @click.prevent="formRemoveMenu(menu)" icon="el-icon-delete"
|
||||
size="small"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
@@ -38,8 +38,8 @@ layout("/layouts/platform.html"){
|
||||
</div>
|
||||
</el-image>
|
||||
</template>
|
||||
<template scope="{row}" v-else-if="column.prop=='containsMenu'">
|
||||
{{firstMenus.filter(v=>row.containsMenu.includes(v.id)).map(v=>v.name).join('、')}}
|
||||
<template scope="{row}" v-else-if="column.prop=='faIcon'">
|
||||
<i :class="row.faIcon"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200px">
|
||||
@@ -62,7 +62,7 @@ layout("/layouts/platform.html"){
|
||||
<el-form-item label="模块名称" prop="name">
|
||||
<el-input v-model="formData.name" maxlength="8"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="模块图标" prop="icon">
|
||||
<el-form-item label="模块图片" prop="icon">
|
||||
<file-upload
|
||||
:value.sync="formData.icon"
|
||||
:upload_number="1"
|
||||
@@ -71,6 +71,10 @@ layout("/layouts/platform.html"){
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="faIcon">
|
||||
<el-input maxlength="100" placeholder="图标" v-model="formData.faIcon"></el-input>
|
||||
<i :class="formData.faIcon" v-if="formData.faIcon"></i>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序编号" prop="sortNum">
|
||||
<el-input-number v-model="formData.sortNum" :min="1" :precision="0"></el-input-number>
|
||||
</el-form-item>
|
||||
@@ -100,14 +104,16 @@ layout("/layouts/platform.html"){
|
||||
platform: [{ required: true, message: "请填写", trigger: ["blur", "change"] }],
|
||||
name: [{ required: true, message: "请填写", trigger: ["blur", "change"] }],
|
||||
icon: [{ required: true, message: "请填写", trigger: ["blur", "change"] }],
|
||||
sortNum: [{ required: true, message: "请输入", trigger: ["blur", "change"] }]
|
||||
sortNum: [{ required: true, message: "请输入", trigger: ["blur", "change"] }],
|
||||
faIcon: [{ required: true, message: "请填写", trigger: ["blur", "change"] }]
|
||||
},
|
||||
firstMenus: [],
|
||||
tableColumns: [
|
||||
{ label: "排序编号", prop: "sortNum", sortable: true },
|
||||
{ label: "所属平台", prop: "platform", sortable: true },
|
||||
{ label: "模块名称", prop: "name" },
|
||||
{ label: "模块图标", prop: "icon" }
|
||||
{ label: "模块图标", prop: "icon" },
|
||||
{ label: "模块图标", prop: "faIcon" }
|
||||
],
|
||||
pageForm: {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.flowName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="flowName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="flowCode" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类"></el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="activityStatus" label="激活状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.activityStatus===1" type="success">激活</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.activityStatus===0" type="info">挂起</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isPublish" label="发布状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isPublish===0" type="info">未发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===1" type="success">已发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===9" type="info">失效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="250px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button type="text" size="mini" @click="onDesign(row)">设计</el-button>
|
||||
<el-button type="text" size="mini" @click="onView(row)">查看</el-button>
|
||||
<el-button v-if="row.isPublish !== 1" type="text" size="mini" @click="onPublish(row)">发布</el-button>
|
||||
<el-button type="text" size="mini" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="flowName" :rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="flowCode" :rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowCode" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="categoryId">
|
||||
<el-select v-model="formData.categoryId" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,195 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="是否发布">
|
||||
<el-select v-model="pageForm.isPublish" placeholder="请选择是否发布" clearable style="width: 100%">
|
||||
<el-option label="全部" :value="null"></el-option>
|
||||
<el-option label="已发布" :value="true"></el-option>
|
||||
<el-option label="未发布" :value="false"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.flowName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.flowCode" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="flowName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="flowCode" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类">
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id === row.category)?.categoryName}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="activityStatus" label="激活状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.activityStatus===1" type="success">激活</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.activityStatus===0" type="info">挂起</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isPublish" label="发布状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isPublish===0" type="info">未发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===1" type="success">已发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===9" type="info">失效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="250px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button v-if="row.isPublish !== 1" type="text" size="mini" @click="onDesign(row)">设计</el-button>
|
||||
<el-button type="text" size="mini" @click="onView(row)">查看</el-button>
|
||||
<el-button v-if="row.isPublish !== 1" type="text" size="mini" @click="onPublish(row)">发布</el-button>
|
||||
<el-button type="text" size="mini" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="flowName" :rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="flowCode" :rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowCode" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category" :rules="{ required: true, message: '请选择流程分类', trigger: 'change' }">
|
||||
<el-select v-model="formData.category" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="designVisible" fullscreen class="design-dialog">
|
||||
<div style="height: calc(100vh - 55px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%" style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd() {
|
||||
this.formData = {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onDesign(row, disabled = false) {
|
||||
this.designVisible = true
|
||||
this.iframeLoaded()
|
||||
this.designUrl = "/warm-flow-ui/index.html?id=" + row.id + "&disabled=" + disabled
|
||||
},
|
||||
|
||||
iframeLoaded() {
|
||||
// iframe监听组件内设计器保存事件
|
||||
window.onmessage = (event) => {
|
||||
switch (event.data.method) {
|
||||
case "close":
|
||||
close()
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("是否确认删除流程定义编码为【" + row.flowCode + "】版本为【" + row.version + "】的数据项?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/warmFlow/definition/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.message)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onView(row) {},
|
||||
// 发布
|
||||
onPublish(row) {
|
||||
this.$confirm(
|
||||
"是否确认发布流程定义编码为【" + row.flowCode + "】版本为【" + row.version + "】的数据项?,发布后会将已发布流程定义改为失效!",
|
||||
"提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}
|
||||
).then(() => {
|
||||
this.$axios.post("/platform/warmFlow/definition/publish", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.message)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios.post("/platform/warmFlow/definition/" + (this.formData.id ? "update" : "insert"), this.formData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.dialogVisible = false
|
||||
this.$message.success(res.message)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.categoryOptions = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,586 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="searchForm.processType" placeholder="所有流程类型" clearable>
|
||||
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tasks"
|
||||
style="width: 100%"
|
||||
:key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<span class="task-title">{{row.instance_ext?.instanceName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="node_name" label="任务节点" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">{{scope.row.node_name}}</div>
|
||||
<div v-else-if="activeTab === 'done'">{{scope.row.node_name}}</div>
|
||||
<div v-else-if="activeTab === 'started'">{{scope.row.assigneeInfo}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="instance_ext.initiatorUserName" label="申请人" min-width="120">
|
||||
<template slot-scope="{row}">{{row.instance_ext?.initiatorUserName}} ({{row.instance_ext?.initiatorLoginName}})</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">
|
||||
<i class="el-icon-time"></i>
|
||||
{{scope.row.create_time}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'done'">
|
||||
<i class="el-icon-check"></i>
|
||||
{{scope.row.update_time}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'started'">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
{{scope.row.create_time}}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row)" size="small" effect="light">{{scope.row.status || '进行中'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
|
||||
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.description" label="任务描述">
|
||||
<div class="description-content">{{currentTask.description}}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
|
||||
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 优化后的分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev,
|
||||
.pagination-container .el-pagination .btn-next {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev:hover,
|
||||
.pagination-container .el-pagination .btn-next:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 3px;
|
||||
font-weight: normal;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__jump {
|
||||
margin-left: 15px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input .el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 任务详情样式 */
|
||||
.task-detail .el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
keyword: "",
|
||||
processType: ""
|
||||
},
|
||||
|
||||
// 流程类型选项
|
||||
processTypes: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 分页
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async getProcessTypes() {
|
||||
try {
|
||||
const res = await $.post("/platform/workflow/todoCenter/process/types")
|
||||
if (res.code === 0) {
|
||||
this.processTypes = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取流程类型失败")
|
||||
console.error("获取流程类型失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
type: this.activeTab,
|
||||
keyword: this.searchForm.keyword,
|
||||
processType: this.searchForm.processType
|
||||
}
|
||||
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/" + this.activeTab, params)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.total = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.searchForm = {
|
||||
keyword: "",
|
||||
processType: ""
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 查看任务详情
|
||||
viewTaskDetail(task) {
|
||||
this.currentTask = task
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async handleTask(task) {
|
||||
if (!task) return
|
||||
|
||||
// 如果有表单链接,直接跳转
|
||||
if (task.taskMobileFormUrl) {
|
||||
window.open(task.taskMobileFormUrl, "_blank")
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态对应的类型
|
||||
getStatusType(task) {
|
||||
if (!task.status) return "info"
|
||||
|
||||
switch (task.status) {
|
||||
case "审批中":
|
||||
return "primary"
|
||||
case "已完成":
|
||||
return "success"
|
||||
case "已通过":
|
||||
return "success"
|
||||
case "已拒绝":
|
||||
return "danger"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
},
|
||||
|
||||
// 表格行类名
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,115 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="流程实例">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="onOpen">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
@sort-change="pageOrder"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
></el-table-column>
|
||||
<el-table-column label="操作" width="150px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="onEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="onDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="60%">
|
||||
<el-form ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
|
||||
<el-input v-model="formData.name" maxlength="100" placeholder="请输入名称" show-word-limit></el-input></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="表达式" prop="expression" :rules="[{ required: true, message: '请输入表达式', trigger: 'blur' }]">
|
||||
<el-input v-model="formData.expression" maxlength="255" placeholder="请输入表达式" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
tableColumns: [
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "表达式", prop: "expression" },
|
||||
{ label: "创建时间", prop: "createdBy" }
|
||||
],
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSave() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios.post("/platform/workflow/expression/" + (this.formData.id ? "update" : "save"), this.formData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.dialogVisible = false
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onOpen() {
|
||||
this.dialogVisible = true
|
||||
this.formData = {}
|
||||
},
|
||||
onEdit(row) {
|
||||
this.dialogVisible = true
|
||||
this.formData = { ...row }
|
||||
},
|
||||
onDelete(id){
|
||||
this.$confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$axios.post('/platform/workflow/expression/delete', {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,48 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>1流程设计器 - JeecgBoot低代码平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" style="padding-top: 1px">
|
||||
<vue-bpmn-designer ref="FlowDesigner" :is-add="true" token="" style="overflow: hidden"></vue-bpmn-designer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
window._CONFIG = {
|
||||
domianURL: "http://localhost:8080/"
|
||||
}
|
||||
</script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/vue/polyfill.min.js?v=1749544752.10"></script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/vue/vue.min.js?v=1749544752.10"></script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/moment/moment-with-locales.min.js?v=1749544752.10"></script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/antd/antd-with-locales.js?v=1749544752.10"></script>
|
||||
<link rel="stylesheet" href="https://api3.boot.jeecg.com/desflow/antd/antd.css?v=1749544752.10" />
|
||||
<link rel="stylesheet" href="https://api3.boot.jeecg.com/desflow/lib/index.css?v=1749544752.10" />
|
||||
<script src="/assets/platform/plugins/bpmn-designer/index.umd.min.js?v=1.0.1"></script>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
processId: "${modelId!}",
|
||||
category: "${category!}"
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted: function () {
|
||||
console.log(this.processId)
|
||||
this.$refs.FlowDesigner.show({
|
||||
processId: this.processId,
|
||||
typeid: this.category
|
||||
})
|
||||
console.log(this.processId)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script></script>
|
||||
</html>
|
||||
@@ -0,0 +1,229 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="流程定义">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="openDesigner()">新建流程模型</el-button>
|
||||
</table-tool>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
@sort-change="pageOrder"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template scope="{row}" v-if="column.prop=='deploymentId'">
|
||||
<el-tag v-if="row.deploymentId" size="mini">已部署</el-tag>
|
||||
<el-tag v-else size="mini">未部署</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="400px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="openDesigner(scope.row)">设计</el-button>
|
||||
<el-button size="mini" type="primary" @click="deploy(scope.row.id)">部署</el-button>
|
||||
<el-button size="mini" type="info" @click="showHistory(scope.row)">历史版本</el-button>
|
||||
<el-button size="mini" type="danger" @click="onDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="dialogVisible" fullscreen>
|
||||
<div style="height: calc(100vh - 115px)">
|
||||
<iframe
|
||||
:src="designerUrl"
|
||||
v-if="dialogVisible"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
allowtransparency="true"
|
||||
allowfullscreen="true"
|
||||
width="100%"
|
||||
height="100%"
|
||||
></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 历史版本对话框 -->
|
||||
<el-dialog title="历史版本" :visible.sync="historyDialogVisible" width="60%">
|
||||
<el-table :data="historyVersions" style="width: 100%" v-loading="historyLoading">
|
||||
<el-table-column label="版本号" prop="version" width="100" align="center"></el-table-column>
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180"></el-table-column>
|
||||
<el-table-column label="最后更新时间" prop="lastUpdateTime" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="viewHistoryXml(scope.row)">查看XML</el-button>
|
||||
<el-button size="mini" type="success" @click="restoreVersion(scope.row)" :disabled="currentModel.version === scope.row.version">恢复</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看XML对话框 -->
|
||||
<el-dialog title="模型XML" :visible.sync="xmlDialogVisible" width="80%">
|
||||
<pre v-if="modelXml" style="max-height: 500px; overflow: auto;">{{ modelXml }}</pre>
|
||||
<div v-else>加载中...</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
tableColumns: [
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "Key", prop: "key" },
|
||||
{ label: "分类", prop: "category" },
|
||||
{ label: "是否部署", prop: "deploymentId" },
|
||||
{ label: "版本", prop: "version" },
|
||||
{ label: "创建时间", prop: "createTime" }
|
||||
],
|
||||
dialogVisible: false,
|
||||
designerUrl: "/platform/workflow/process/definition' + modelId",
|
||||
historyDialogVisible: false,
|
||||
historyVersions: [],
|
||||
historyLoading: false,
|
||||
currentModel: {},
|
||||
xmlDialogVisible: false,
|
||||
modelXml: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openDesigner(row = null) {
|
||||
this.dialogVisible = true
|
||||
if (row) {
|
||||
this.designerUrl = "/platform/workflow/designer?modelId=" + row.id + "&category=" + row.category
|
||||
} else {
|
||||
this.designerUrl = "/platform/workflow/designer?modelId=&category="
|
||||
}
|
||||
},
|
||||
deploy(modelId) {
|
||||
this.$confirm("确定要部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$axios.post("/platform/workflow/model/deploy", { modelId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
onDelete(modelId) {
|
||||
this.$confirm("确定要删除吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$axios.post("/platform/workflow/model/delete", { modelId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
// 显示历史版本
|
||||
showHistory(row) {
|
||||
this.currentModel = row;
|
||||
this.historyDialogVisible = true;
|
||||
this.historyLoading = true;
|
||||
|
||||
// 调用后端接口获取历史版本
|
||||
this.$axios.get("/platform/workflow/model/history", {
|
||||
params: { modelKey: row.key }
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.historyVersions = res.data;
|
||||
} else {
|
||||
this.$message.error(res.msg || "获取历史版本失败");
|
||||
}
|
||||
this.historyLoading = false;
|
||||
}).catch(() => {
|
||||
this.historyLoading = false;
|
||||
this.$message.error("获取历史版本失败");
|
||||
});
|
||||
},
|
||||
// 查看历史版本XML
|
||||
viewHistoryXml(row) {
|
||||
this.xmlDialogVisible = true;
|
||||
this.modelXml = "加载中...";
|
||||
|
||||
this.$axios.get("/platform/workflow/model/history/xml", {
|
||||
params: { modelId: row.id }
|
||||
}).then(res => {
|
||||
this.modelXml = res.data;
|
||||
}).catch(() => {
|
||||
this.modelXml = "加载失败";
|
||||
this.$message.error("获取模型XML失败");
|
||||
});
|
||||
},
|
||||
// 恢复历史版本
|
||||
restoreVersion(row) {
|
||||
this.$confirm('确定要恢复到版本'+row.version+'吗?', "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
// 获取历史版本的XML
|
||||
this.$axios.get("/platform/workflow/model/history/xml", {
|
||||
params: { modelId: row.id }
|
||||
}).then(xml => {
|
||||
// 使用历史版本的XML保存为新版本
|
||||
this.$axios.post("/platform/workflow/model/save", {
|
||||
modelId: this.currentModel.id,
|
||||
processName: this.currentModel.name,
|
||||
processKey: this.currentModel.key,
|
||||
category: this.currentModel.category,
|
||||
bpmnXml: xml
|
||||
}).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success("版本恢复成功");
|
||||
this.historyDialogVisible = false;
|
||||
this.pageData();
|
||||
} else {
|
||||
this.$message.error(res.msg || "版本恢复失败");
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error("版本恢复失败");
|
||||
});
|
||||
}).catch(() => {
|
||||
this.$message.error("获取历史版本XML失败");
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,123 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="流程定义"></table-tool>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
@sort-change="pageOrder"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template scope="{row}" v-if="column.prop=='suspended'">
|
||||
<el-tag v-if="row.suspended" size="mini" type="info">已挂起</el-tag>
|
||||
<el-tag v-else size="mini" type="success">已激活</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="openVersion(scope.row)">版本管理</el-button>
|
||||
<el-button size="mini" type="primary" @click="openNodeConfig(scope.row)">配置</el-button>
|
||||
<!-- <el-button size="mini" type="primary" @click="activate(scope.row.id)">激活</el-button>-->
|
||||
<!-- <el-button size="mini" type="danger" @click="suspend(scope.row.id)">挂起</el-button>-->
|
||||
<!-- <el-button size="mini" type="danger" @click="onDelete(scope.row.deploymentId)">删除</el-button>-->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="dialogVisible" width="80%">
|
||||
<iframe
|
||||
:src="designerUrl"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
allowtransparency="true"
|
||||
allowfullscreen="true"
|
||||
width="100%"
|
||||
height="600px"
|
||||
></iframe>
|
||||
</el-dialog>
|
||||
|
||||
<process-version ref="versionRef"></process-version>
|
||||
<node-config ref="nodeConfigRef"></node-config>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--#include('processVersion.js'){}#-->
|
||||
<!--#include('nodeConfig.js'){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
"process-version": processVersion,
|
||||
"node-config": nodeConfig
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableColumns: [
|
||||
{ label: "ID", prop: "id" },
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "Key", prop: "key" },
|
||||
{ label: "分类", prop: "category" },
|
||||
{ label: "状态", prop: "suspended" },
|
||||
{ label: "版本", prop: "version" },
|
||||
{ label: "创建时间", prop: "createTime" }
|
||||
],
|
||||
dialogVisible: false,
|
||||
designerUrl: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openVersion(row) {
|
||||
this.$refs.versionRef.onOpen(row.key)
|
||||
},
|
||||
openNodeConfig(row){
|
||||
this.$refs.nodeConfigRef.onOpen(row.id)
|
||||
},
|
||||
|
||||
|
||||
deploy(modelId) {
|
||||
this.$confirm("确定要部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/deploy", { modelId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("部署成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,93 @@
|
||||
const nodeConfig = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog title="节点配置" :visible.sync="visible" fullscreen append-to-body>
|
||||
<el-table :data="tableData" border>
|
||||
<el-table-column prop="nodeName" label="节点名称" />
|
||||
<el-table-column prop="nodeId" label="节点编码" />
|
||||
<el-table-column prop="pcFormUrl" label="PC表单地址" />
|
||||
<el-table-column prop="mobileFormUrl" label="移动端表单地址" />
|
||||
<el-table-column prop="allowSelectNextUser" label="选择下一步处理人" />
|
||||
<el-table-column prop="allowSelectCcUser" label="是否允许选择抄送人" />
|
||||
<el-table-column prop="allowTransfer" label="是否允许转办" />
|
||||
<el-table-column prop="allowReject" label="是否允许驳回" />
|
||||
<el-table-column label="操作" width="150px">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="onEdit(scope.row)">配置</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="节点配置" :visible.sync="editVisible" width="700px" append-to-body>
|
||||
<el-form ref="form" :model="formData" label-width="150px">
|
||||
<el-form-item label="节点名称">
|
||||
<el-input v-model="formData.nodeName" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="节点编码">
|
||||
<el-input v-model="formData.nodeId" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="PC表单地址" prop="pcFormUrl">
|
||||
<el-input v-model="formData.pcFormUrl" />
|
||||
</el-form-item>
|
||||
<el-form-item label="移动端表单地址" prop="mobileFormUrl">
|
||||
<el-input v-model="formData.mobileFormUrl" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择下一步处理人" prop="allowSelectNextUser">
|
||||
<el-switch v-model="formData.allowSelectNextUser" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否允许选择抄送人" prop="allowSelectCcUser">
|
||||
<el-switch v-model="formData.allowSelectCcUser" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否允许转办" prop="allowTransfer">
|
||||
<el-switch v-model="formData.allowTransfer" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否允许驳回" prop="allowReject">
|
||||
<el-switch v-model="formData.allowReject" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="editVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSave">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</el-dialog>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
title: "标题",
|
||||
processDefinitionId: null,
|
||||
tableData: [],
|
||||
|
||||
editVisible: false,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(processDefinitionId) {
|
||||
this.visible = true
|
||||
this.processDefinitionId = processDefinitionId
|
||||
this.loadData()
|
||||
},
|
||||
loadData() {
|
||||
this.$axios.post("/platform/workflow/process/definition/userTasksConfig/" + this.processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
onEdit(row) {
|
||||
this.editVisible = true
|
||||
this.formData = { ...row }
|
||||
},
|
||||
onSave() {
|
||||
this.$axios.post("/platform/workflow/process/definition/saveUserTasksConfig", this.formData).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.editVisible = false
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
const processVersion = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog title="版本信息" :visible.sync="visible" fullscreen>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="key" prop="key"></el-table-column>
|
||||
<el-table-column label="版本" prop="version"></el-table-column>
|
||||
<el-table-column label="状态" prop="suspended">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.suspended" size="mini" type="info">挂起</el-tag>
|
||||
<el-tag v-else size="mini" type="success">激活</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部署状态" prop="deploymentValid">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.deploymentValid" size="mini" type="success">有效</el-tag>
|
||||
<el-tag v-else size="mini" type="danger">无效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部署时间" prop="deploymentTime">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.deploymentTime ? new Date(scope.row.deploymentTime).toLocaleString() : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300px">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openDiagram(scope.row.id)">流程图</el-button>
|
||||
<el-button size="mini" type="text" v-if="scope.row.suspended" @click="activate(scope.row.id)">
|
||||
激活
|
||||
</el-button>
|
||||
<el-button size="mini" type="text" v-if="!scope.row.suspended" @click="suspend(scope.row.id)">
|
||||
挂起
|
||||
</el-button>
|
||||
<el-button size="mini" type="text" @click="onDelete(scope.row.deploymentId)">删除</el-button>
|
||||
<el-button size="mini" type="text" v-if="!scope.row.deploymentValid" @click="cleanupInvalidDefinition(scope.row.id)">清理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="流程图" :visible.sync="diagramVisible" width="70%" append-to-body>
|
||||
<img :src="diagramUrl" alt="">
|
||||
</el-dialog>
|
||||
|
||||
</el-dialog>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
tableData: [],
|
||||
processKey: null,
|
||||
diagramVisible: false,
|
||||
diagramUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(processKey) {
|
||||
this.visible = true
|
||||
this.processKey = processKey
|
||||
this.loadData()
|
||||
},
|
||||
loadData() {
|
||||
this.$axios.post("/platform/workflow/process/definition/versions", { processKey: this.processKey }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
activate(processDefinitionId) {
|
||||
this.$confirm("确定要激活吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/activate/" + processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("激活成功")
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
suspend(processDefinitionId) {
|
||||
this.$confirm("确定要挂起吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/suspend/" + processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("挂起成功")
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(deploymentId) {
|
||||
this.$confirm("确定要删除吗?删除后将无法恢复", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
// 添加级联删除选项
|
||||
this.$confirm("是否级联删除?(级联删除会同时删除相关的流程实例)", "提示", {
|
||||
confirmButtonText: "是",
|
||||
cancelButtonText: "否",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
// 级联删除
|
||||
this.$axios.post("/platform/workflow/process/definition/delete/" + deploymentId + "?cascade=true").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.error(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
// 非级联删除
|
||||
this.$axios.post("/platform/workflow/process/definition/delete/" + deploymentId + "?cascade=false").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.error(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
cleanupInvalidDefinition(processDefinitionId) {
|
||||
this.$confirm("确定要清理此无效流程定义吗?此操作将从数据库中删除此记录。", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/cleanup/" + processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("清理成功")
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
openDiagram(processDefinitionId) {
|
||||
this.diagramVisible = true
|
||||
this.diagramUrl = "/platform/workflow/process/definition/diagram/" + processDefinitionId
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="searchForm.category" placeholder="所有流程类型" clearable>
|
||||
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tasks"
|
||||
style="width: 100%"
|
||||
:key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="task-title">{{scope.row.processInstanceName || scope.row.title}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="applyUserName" label="申请人" min-width="120"></el-table-column>
|
||||
<el-table-column label="时间" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">
|
||||
<i class="el-icon-time"></i>
|
||||
{{scope.row.createTime}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'done'">
|
||||
<i class="el-icon-check"></i>
|
||||
{{scope.row.endTime}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'started'">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
{{scope.row.startTime}}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row)" size="small" effect="light">{{scope.row.status || '进行中'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
|
||||
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.description" label="任务描述">
|
||||
<div class="description-content">{{currentTask.description}}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
|
||||
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 优化后的分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev,
|
||||
.pagination-container .el-pagination .btn-next {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev:hover,
|
||||
.pagination-container .el-pagination .btn-next:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 3px;
|
||||
font-weight: normal;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__jump {
|
||||
margin-left: 15px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input .el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 任务详情样式 */
|
||||
.task-detail .el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
keyword: "",
|
||||
category: ""
|
||||
},
|
||||
|
||||
// 流程类型选项
|
||||
processTypes: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 分页
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async getProcessTypes() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/category")
|
||||
if (res.code === 0) {
|
||||
this.processTypes = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取流程类型失败")
|
||||
console.error("获取流程类型失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
type: this.activeTab,
|
||||
keyword: this.searchForm.keyword,
|
||||
category: this.searchForm.category
|
||||
}
|
||||
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/" + this.activeTab, params)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.total = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.searchForm = {
|
||||
keyword: "",
|
||||
category: ""
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 查看任务详情
|
||||
viewTaskDetail(task) {
|
||||
this.currentTask = task
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async handleTask(task) {
|
||||
if (!task) return
|
||||
|
||||
// 如果有表单链接,直接跳转
|
||||
if (task.taskMobileFormUrl) {
|
||||
window.open(task.taskMobileFormUrl, "_blank")
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态对应的类型
|
||||
getStatusType(task) {
|
||||
if (!task.status) return "info"
|
||||
|
||||
switch (task.status) {
|
||||
case "审批中":
|
||||
return "primary"
|
||||
case "已完成":
|
||||
return "success"
|
||||
case "已通过":
|
||||
return "success"
|
||||
case "已拒绝":
|
||||
return "danger"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
},
|
||||
|
||||
// 表格行类名
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="approvalApp" v-cloak>
|
||||
<snaker-flow
|
||||
:task_id="taskId"
|
||||
:instance_id="instanceId"
|
||||
:business_id="businessId"
|
||||
:pjax_urls="{
|
||||
applicationInfo: '/platform/article/write/index_',
|
||||
taskForm: '/platform/article/write/index_'
|
||||
}"
|
||||
:pjax_config="{
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
}"
|
||||
></snaker-flow>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#approvalApp",
|
||||
data() {
|
||||
return {
|
||||
taskId: null,
|
||||
instanceId: null,
|
||||
businessId: null,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskSubmitted() {},
|
||||
handleCancel() {
|
||||
window.history.back()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskId = new URLSearchParams(window.location.search).get("taskId")
|
||||
this.instanceId = new URLSearchParams(window.location.search).get("instanceId")
|
||||
this.businessId = new URLSearchParams(window.location.search).get("businessId")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<div id="article_branch_union_approval_form">
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审批意见" prop="approvalOpinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="电子签名" prop="approvalSignature" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">-->
|
||||
<!-- <pc-signature v-model="formData.approvalSignature"></pc-signature>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
<snaker-flow-task-form-action @task-action="handleTaskAction"
|
||||
@cancel="handleCancel">
|
||||
</snaker-flow-task-form-action>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#article_branch_union_approval_form",
|
||||
data() {
|
||||
return {
|
||||
taskId: GetQueryString("taskId"),
|
||||
formData: {
|
||||
approval: "",
|
||||
approvalOpinion: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskAction(val) {
|
||||
console.log(val)
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(val)
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage({
|
||||
type: "task-complete"
|
||||
}, "*")
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCancel(val) {
|
||||
console.log(val)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
+29
-20
@@ -36,11 +36,17 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="unitName" label="投稿人单位"></el-table-column>
|
||||
<el-table-column prop="unionName" label="投稿人工会"></el-table-column>
|
||||
<el-table-column prop="submitTime" label="投稿时间"></el-table-column>
|
||||
<el-table-column label="当前节点" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200px" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="row.processInstanceTaskStatus==='ACTIVE'" @click="openApproval(row)" size="mini" type="primary">
|
||||
<el-button v-if="row.taskState === 10" @click="openApproval(row)" size="mini" type="primary">
|
||||
审核
|
||||
</el-button>
|
||||
<el-button
|
||||
@@ -57,19 +63,25 @@ layout("/layouts/platform.html"){
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #public>
|
||||
<el-dialog title="" :visible.sync="showApprovalForm" width="90%">
|
||||
<info ref="infoRef"></info>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<template #public>
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.processInstanceNodeName}}</div>
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审核意见" prop="approvalOpinion" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button plain @click="$refs.guava.index()">取消</el-button>
|
||||
<el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>
|
||||
<el-button type="primary" @click="doApproval('PASS')">同意</el-button>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
@@ -89,7 +101,8 @@ layout("/layouts/platform.html"){
|
||||
pageForm: {
|
||||
approval: false
|
||||
},
|
||||
showApprovalForm: false
|
||||
showApprovalForm: false,
|
||||
taskId: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -100,11 +113,7 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
openApproval(row) {
|
||||
this.$refs.guava.public(() => {
|
||||
this.$refs.infoRef.onOpen(row.id)
|
||||
this.formData = row.approvalParam
|
||||
this.showApprovalForm = true
|
||||
})
|
||||
window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo)
|
||||
},
|
||||
doApproval(approvalType) {
|
||||
this.formData.bpmTaskApprovalType = approvalType
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<div id="article-full-form">
|
||||
<el-descriptions :column="2" border class="flow-task-form">
|
||||
<el-descriptions-item label="投稿人姓名">{{ viewData.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工号">{{ viewData.loginName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人单位">{{ viewData.unitName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工会">{{ viewData.unionName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人联系方式">{{ viewData.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="稿件修改人联系方式">{{ viewData.mobile2 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿标题" :span="2">{{ viewData.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿说明" :span="2">{{ viewData.excerpt }}</el-descriptions-item>
|
||||
<el-descriptions-item label="稿件" :span="2">
|
||||
<file-preview :files="viewData.files" complete_result></file-preview>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<template v-for="task in doneTasks">
|
||||
<div class="task-panel mt10">
|
||||
<div class="task-panel-header">
|
||||
{{ task.displayName }}
|
||||
</div>
|
||||
<el-descriptions border class="flow-task-form" :column="2" :key="task.id">
|
||||
<el-descriptions-item label="办理用户">{{ task.operator }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#article-full-form",
|
||||
data() {
|
||||
return {
|
||||
businessId: GetQueryString("businessId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
viewData: {},
|
||||
doneTasks: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
info() {
|
||||
this.$axios.post("/platform/article/common/info", { id: this.businessId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
getDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { instanceId: this.instanceId }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.info()
|
||||
this.getDoneTasks()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.task-panel{
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-panel-header{
|
||||
background: rgb(250, 250, 250);
|
||||
border: 1px solid rgb(228, 231, 237);
|
||||
border-bottom: none;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -41,16 +41,19 @@ const info = {
|
||||
v-if="task.extVariable.bpmTaskApprovalType === 'REJECT'">拒绝
|
||||
</el-tag>
|
||||
<template v-if="task.extVariable.bpmTaskApprovalType === 'DYNAMIC'">
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_START'" type="danger" size="mini">
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_START'" type="danger"
|
||||
size="mini">
|
||||
退回至投稿人
|
||||
<span v-if="task.extVariable.processAgain===false">不需要重新走流程</span>
|
||||
<span v-if="task.extVariable.processAgain===true">需要重新走流程</span>
|
||||
</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'PASS'" type="success" size="mini">同意</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'PASS'" type="success" size="mini">同意
|
||||
</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_CLUB'" type="danger" size="mini">
|
||||
退回到协会
|
||||
</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_UNION'" type="danger" size="mini">
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_UNION'" type="danger"
|
||||
size="mini">
|
||||
退回到分工会
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -72,8 +75,8 @@ const info = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(id) {
|
||||
this.$axios.post("/platform/article/common/info", { id }).then((res) => {
|
||||
onOpen(row) {
|
||||
this.$axios.post("/platform/article/common/info", { id: row.businessKey }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = res.data
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ layout("/layouts/platform.html"){
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy" placeholder="年度" style="width: 100%"></el-date-picker>
|
||||
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy" placeholder="年度"
|
||||
style="width: 100%"></el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="标题">
|
||||
<el-input v-model="pageForm.title" placeholder="标题" clearable></el-input>
|
||||
@@ -25,27 +26,25 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="unitName" label="投稿人单位"></el-table-column>
|
||||
<el-table-column prop="unionName" label="投稿人工会"></el-table-column>
|
||||
<el-table-column prop="submitTime" label="投稿时间"></el-table-column>
|
||||
<el-table-column label="当前节点" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="300px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="[10,30,34,38,50,65].includes(row.processInstanceNodeCode)" @click="onEdit(row.id)" size="mini" type="primary">
|
||||
<el-button v-if="row.taskKey === 'apply' || !row.instanceId" @click="onEdit(row)" size="mini"
|
||||
type="primary">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="[20].includes(row.processInstanceNodeCode) && !row.nextTaskIsComplete"
|
||||
size="mini"
|
||||
type="danger"
|
||||
@click="onRevoke(row.id)"
|
||||
>
|
||||
撤销
|
||||
<el-button v-if="['waiting'].includes(row.flow_status)" size="mini" type="danger"
|
||||
@click="onRevoke(row.id)">撤销
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="[10,30,34,50,70].includes(row.processInstanceNodeCode)||$auth.hasRoleOr('SYSADMIN')"
|
||||
@click="onDelete(row.id)"
|
||||
size="mini"
|
||||
type="danger"
|
||||
>
|
||||
<el-button v-if="row.taskKey === 'apply' || !row.instanceId"
|
||||
@click="onDelete(row.id)" size="mini" type="danger">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -77,8 +76,19 @@ layout("/layouts/platform.html"){
|
||||
this.$refs.infoRef.onOpen(id)
|
||||
})
|
||||
},
|
||||
onEdit(id) {
|
||||
this.$store.dispatch("pjaxRoute", "/platform/article/write?id=" + id)
|
||||
onEdit(row) {
|
||||
window.open("/flow/common/approval/form?taskId=" + (row.taskId || "") + "&instanceId=" + (row.instanceId || "") + "&businessId=" + row.id
|
||||
+ "&defineKey=XWTG")
|
||||
|
||||
// $.pjax({
|
||||
// url: "/platform/article/write?id=" + businessNo + "&taskId=" + taskId + "&instanceId=" + instanceId,
|
||||
// container: "#sub-app-container-main-content",
|
||||
// maxCacheLength: 0,
|
||||
// push: false,
|
||||
// replace: true,
|
||||
// fragment: "#sub-app-container-main-content",
|
||||
// timeout: 8000
|
||||
// })
|
||||
},
|
||||
onRevoke(id) {
|
||||
this.$confirm("您确定要撤销申请吗?", "提示", {
|
||||
@@ -86,7 +96,7 @@ layout("/layouts/platform.html"){
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/article/mine/revokeApply", { id }).then((resp) => {
|
||||
this.$axios.post("/platform/article/write/revokeApply", { id }).then((resp) => {
|
||||
this.$message.success(resp.msg)
|
||||
this.doSearch()
|
||||
})
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<div id="article_school_union_approval_form">
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审批意见" prop="approvalOpinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="电子签名" prop="approvalSignature" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">-->
|
||||
<!-- <pc-signature v-model="formData.approvalSignature"></pc-signature>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
<snaker-flow-task-form-action @task-action="handleTaskAction"
|
||||
@cancel="handleCancel">
|
||||
</snaker-flow-task-form-action>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#article_school_union_approval_form",
|
||||
data() {
|
||||
return {
|
||||
taskId: GetQueryString("taskId"),
|
||||
formData: {
|
||||
approval: "",
|
||||
approvalOpinion: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskAction(val) {
|
||||
console.log(val)
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(val)
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage({
|
||||
type: "task-complete"
|
||||
}, "*")
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCancel(val) {
|
||||
console.log(val)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
+39
-53
@@ -7,26 +7,18 @@ layout("/layouts/platform.html"){
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy" placeholder="年度" style="width: 100%"></el-date-picker>
|
||||
<el-date-picker
|
||||
:clearable="true"
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="年度"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="标题">
|
||||
<el-input v-model="pageForm.title" placeholder="标题" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属单位:">
|
||||
<el-select v-model="pageForm.unitId" placeholder="请选择所属单位" filterable clearable style="width: 100%">
|
||||
<el-option v-for="item in unitOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="投稿来源:">
|
||||
<el-select v-model="pageForm.origin" placeholder="请选择投稿来源" filterable clearable style="width: 100%">
|
||||
<el-option v-for="item in dict.type.ARTICLE_ORIGIN" :key="item.code" :label="item.label" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
@@ -42,25 +34,19 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="loginName" label="投稿人工号"></el-table-column>
|
||||
<el-table-column prop="userName" label="投稿人姓名"></el-table-column>
|
||||
<el-table-column prop="unitName" label="投稿人单位"></el-table-column>
|
||||
<el-table-column
|
||||
prop="unionName"
|
||||
label="投稿人工会"
|
||||
v-if="$auth.hasRoleOr(['SCHOOL_UNION_ARTICLE_FGH_ADMIN','SYSADMIN'])"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="clubName"
|
||||
label="投稿人协会"
|
||||
v-if="$auth.hasRoleOr(['SCHOOL_UNION_ARTICLE_CLUB_ADMIN','SYSADMIN'])"
|
||||
></el-table-column>
|
||||
<el-table-column prop="unionName" label="投稿人工会"></el-table-column>
|
||||
<el-table-column prop="submitTime" label="投稿时间"></el-table-column>
|
||||
<el-table-column prop="mode" label="投稿来源">
|
||||
<template slot-scope="{row}">{{dict.type.ARTICLE_ORIGIN.find(v=>v.code===row.origin)?.label}}</template>
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前节点" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="300px">
|
||||
<el-table-column label="操作" width="200px" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="row.processInstanceTaskStatus==='ACTIVE'" @click="openApproval(row)" size="mini" type="primary">
|
||||
<el-button v-if="row.taskState === 10" @click="openApproval(row)" size="mini" type="primary">
|
||||
审核
|
||||
</el-button>
|
||||
<el-button
|
||||
@@ -77,19 +63,25 @@ layout("/layouts/platform.html"){
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #public>
|
||||
<el-dialog title="" :visible.sync="showApprovalForm" width="90%">
|
||||
<info ref="infoRef"></info>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<template #public>
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.processInstanceNodeName}}</div>
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审核意见" prop="approvalOpinion" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button plain @click="$refs.guava.index()">取消</el-button>
|
||||
<el-button type="danger" @click="doApproval('BACK')">退回到编辑初审</el-button>
|
||||
<el-button type="primary" @click="doApproval('PASS')">同意</el-button>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
@@ -103,14 +95,14 @@ layout("/layouts/platform.html"){
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
dicts: ["ARTICLE_ORIGIN"],
|
||||
components: { info },
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
approval: false
|
||||
},
|
||||
showApprovalForm: false
|
||||
showApprovalForm: false,
|
||||
taskId: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -121,18 +113,14 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
openApproval(row) {
|
||||
this.$refs.guava.public(() => {
|
||||
this.$refs.infoRef.onOpen(row.id)
|
||||
this.formData = row.approvalParam
|
||||
this.showApprovalForm = true
|
||||
})
|
||||
window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo)
|
||||
},
|
||||
doApproval(approvalType) {
|
||||
this.formData.bpmTaskApprovalType = approvalType
|
||||
this.$refs.approvalFormRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios
|
||||
.post("/platform/article/schoolUnionApproval/approval", {
|
||||
.post("/platform/article/branchUnionApproval/approval", {
|
||||
approval: JSON.stringify(this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -151,7 +139,7 @@ layout("/layouts/platform.html"){
|
||||
cancelButtonText: "取消",
|
||||
type: "info"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/article/schoolUnionApproval/revoke", { taskId }).then((res) => {
|
||||
this.$axios.post("/platform/article/branchUnionApproval/revoke", { taskId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
@@ -161,8 +149,6 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
|
||||
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -55,11 +55,11 @@ layout("/layouts/platform.html"){
|
||||
</el-form-item>
|
||||
<el-form-item prop="files" label="稿件">
|
||||
<file-upload
|
||||
:value.sync="formData.files"
|
||||
:upload_number="5"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
upload_mode="drag"
|
||||
:value.sync="formData.files"
|
||||
:upload_number="5"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
upload_mode="drag"
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -86,15 +86,17 @@ layout("/layouts/platform.html"){
|
||||
data() {
|
||||
return {
|
||||
id: GetQueryString("id"),
|
||||
taskId: GetQueryString("taskId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
formData: {},
|
||||
formRules: {
|
||||
title: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
excerpt: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
clubId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
files: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
origin: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
mobile: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
mobile2: [{required: true, message: "必填", trigger: ["change", "blur"]}]
|
||||
title: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
excerpt: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
clubId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
files: [{ required: false, message: "必填", trigger: ["change", "blur"] }],
|
||||
origin: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile2: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
},
|
||||
permission: {}
|
||||
}
|
||||
@@ -106,7 +108,11 @@ layout("/layouts/platform.html"){
|
||||
this.$message.warning("请填写标题")
|
||||
return
|
||||
}
|
||||
this.$axios.post("/platform/article/write/save", {article: JSON.stringify(this.formData)}).then((res) => {
|
||||
this.$axios.post("/platform/article/write/save", {
|
||||
article: JSON.stringify(this.formData),
|
||||
taskId: this.taskId,
|
||||
instanceId: this.instanceId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.$store.dispatch("pjaxRoute", "/platform/article/mine")
|
||||
@@ -117,7 +123,11 @@ layout("/layouts/platform.html"){
|
||||
doSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios.post("/platform/article/write/submit", {article: JSON.stringify(this.formData)}).then((res) => {
|
||||
this.$axios.post("/platform/article/write/submit", {
|
||||
article: JSON.stringify(this.formData),
|
||||
taskId: this.taskId,
|
||||
instanceId: this.instanceId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("提交成功")
|
||||
this.$store.dispatch("pjaxRoute", "/platform/article/mine")
|
||||
@@ -137,13 +147,13 @@ layout("/layouts/platform.html"){
|
||||
|
||||
init() {
|
||||
if (this.id) {
|
||||
this.$axios.post("/platform/article/write/get", {id: this.id}).then((res) => {
|
||||
this.$axios.post("/platform/article/write/get", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const {username, loginname, id, unit, union, mobile} = this.$store.state.user
|
||||
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
|
||||
this.formData = {
|
||||
userName: username,
|
||||
loginName: loginname,
|
||||
@@ -153,7 +163,7 @@ layout("/layouts/platform.html"){
|
||||
unionId: union?.id,
|
||||
unionName: union?.name,
|
||||
mobile: mobile,
|
||||
mobile2: mobile,
|
||||
mobile2: mobile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<div id="artile_write">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":" class="flow-task-form">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="投稿人姓名">{{formData.userName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工号">{{formData.loginName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人单位">{{formData.unitName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工会">{{formData.unionName}}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿来源" :span="2">
|
||||
<el-form-item prop="origin" label="投稿来源" label-width="0">
|
||||
<el-radio-group v-model="formData.origin" size="small">
|
||||
<el-radio border v-for="item in origins" :label="item.code" :key="item.code">{{item.name}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿人协会" v-if="formData.origin === 'ARTICLE_ORIGIN_CLUB'">
|
||||
<el-form-item prop="clubId" label="投稿人协会" v-if="formData.origin === 'ARTICLE_ORIGIN_CLUB'">
|
||||
<el-select v-model="formData.clubId" placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="item in permission?.clubs" :key="item.id" :label="item.clubName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿人联系方式" :span="2">
|
||||
<el-form-item prop="title" label="投稿标题">
|
||||
<el-input v-model="formData.title" maxlength="100" show-word-limit placeholder="请输入标题"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿人联系方式" :span="2">
|
||||
<el-form-item prop="mobile" label="投稿人联系方式">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入投稿人联系方式"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="修改人联系方式" :span="2">
|
||||
<el-form-item prop="mobile2" label="修改人联系方式">
|
||||
<el-input v-model="formData.mobile2" placeholder="请输入修改人联系方式"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿说明" :span="2">
|
||||
<el-form-item prop="excerpt" label="投稿说明">
|
||||
<el-input v-model="formData.excerpt" type="textarea" maxlength="500" show-word-limit placeholder="请输入投稿说明"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="稿件" :span="2">
|
||||
<file-upload
|
||||
:value.sync="formData.files"
|
||||
:upload_number="5"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
upload_mode="drag"
|
||||
></file-upload>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form>
|
||||
<snaker-flow-task-form-action @task-action="handleTaskAction" @save-draft="handleSaveDraft" @cancel="handleCancel"></snaker-flow-task-form-action>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#artile_write",
|
||||
store,
|
||||
dicts: ["ARTICLE_ORIGIN"],
|
||||
computed: {
|
||||
origins() {
|
||||
if (this.dict?.type?.ARTICLE_ORIGIN && this.permission && this.permission.roles) {
|
||||
return this.dict.type.ARTICLE_ORIGIN.filter((item) => this.permission && this.permission.roles.includes(item.code))
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: GetQueryString("businessId"),
|
||||
taskId: GetQueryString("taskId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
formData: {},
|
||||
formRules: {
|
||||
title: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
excerpt: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
clubId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
files: [{ required: false, message: "必填", trigger: ["change", "blur"] }],
|
||||
origin: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile2: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
},
|
||||
permission: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (this.id) {
|
||||
this.$axios.post("/platform/article/write/get", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
|
||||
this.formData = {
|
||||
userName: username,
|
||||
loginName: loginname,
|
||||
userId: id,
|
||||
unitId: unit?.id,
|
||||
unitName: unit?.name,
|
||||
unionId: union?.id,
|
||||
unionName: union?.name,
|
||||
mobile: mobile,
|
||||
mobile2: mobile
|
||||
}
|
||||
}
|
||||
},
|
||||
checkPermission() {
|
||||
this.$axios.post("/platform/article/write/checkPermission").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.permission = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleTaskAction(val) {
|
||||
console.log(val)
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios
|
||||
.post("/flow/common/startInstanceAndExecute", {
|
||||
...val,
|
||||
bizData: JSON.stringify(this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "task-complete"
|
||||
},
|
||||
"*"
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleSaveDraft() {
|
||||
this.$message.success("保存成功")
|
||||
this.$refs.formRef.validateField(["title"], (errMsg) => {
|
||||
if (errMsg) {
|
||||
this.$message.warning("请填写标题")
|
||||
return
|
||||
}
|
||||
this.$axios
|
||||
.post("/flow/common/startInstance", {
|
||||
...val,
|
||||
bizData: JSON.stringify(this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "task-complete"
|
||||
},
|
||||
"*"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleCancel() {}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.checkPermission()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak></div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,878 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(0, 78, 100);
|
||||
--primary-light: rgba(0, 78, 100, 0.1);
|
||||
--secondary-color: #37A6BD;
|
||||
--text-color: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-light: #999999;
|
||||
--bg-color: #f5f7fa;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #eeeeee;
|
||||
--success-color: #07c160;
|
||||
--warning-color: #ff976a;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 筛选区域样式 */
|
||||
.filter-section {
|
||||
background-color: var(--card-bg);
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.filter-section.collapsed {
|
||||
max-height: 84px;
|
||||
}
|
||||
|
||||
.filter-section.expanded {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.filter-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.filter-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.filter-toggle {
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-toggle .van-icon {
|
||||
transition: transform 0.3s;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.filter-toggle .van-icon.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.filter-content.hidden {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.filter-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-right: 10px;
|
||||
min-width: 60px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-tag {
|
||||
padding: 4px 10px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 6px;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.filter-tag.active {
|
||||
background-color: var(--primary-light);
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-search {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.filter-search .van-search {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-search .van-search__content {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.suggestion-list {
|
||||
padding: 16px;
|
||||
background-color: var(--bg-color);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.van-pull-refresh, .van-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suggestion-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.suggestion-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.suggestion-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.suggestion-status {
|
||||
font-size: 12px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
max-height: 3.2em;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.suggestion-submitter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.suggestion-submitter .van-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.suggestion-unit {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.suggestion-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
border-top: 1px solid #f5f5f5;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.suggestion-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suggestion-time .van-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.suggestion-action {
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.suggestion-action .van-icon {
|
||||
font-size: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 64px 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #ddd;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 15px;
|
||||
color: var(--text-light);
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.detail-popup {
|
||||
padding: 24px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-light);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
color: var(--text-color);
|
||||
line-height: 1.8;
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.submitter-info {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--text-secondary);
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-reply {
|
||||
background-color: #f9f9f9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.detail-attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.reply-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.reply-textarea {
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
font-size: 14px;
|
||||
background-color: var(--card-bg);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reply-attachments {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reply-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.no-reply {
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.no-reply-icon {
|
||||
font-size: 36px;
|
||||
color: #ccc;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-reply-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.van-button--primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 下拉刷新和上拉加载样式 */
|
||||
.van-pull-refresh__track {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.van-list {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* 统计面板 */
|
||||
.stats-panel {
|
||||
background-color: var(--card-bg);
|
||||
padding: 16px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="意见箱管理" left-arrow @click-left="history.go(-1)" fixed placeholder></van-nav-bar>
|
||||
|
||||
<!-- 统计面板 -->
|
||||
<div class="stats-panel">
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.total }}</div>
|
||||
<div class="stats-label">总意见数</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.pending }}</div>
|
||||
<div class="stats-label">待回复</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.replied }}</div>
|
||||
<div class="stats-label">已回复</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.today }}</div>
|
||||
<div class="stats-label">今日新增</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-section" :class="filters.isCollapsed ? 'collapsed' : 'expanded'">
|
||||
<div class="filter-header">
|
||||
<div class="filter-title">筛选条件</div>
|
||||
<div class="filter-toggle" @click="toggleFilterCollapse">
|
||||
<span>{{ filters.isCollapsed ? '展开' : '收起' }}</span>
|
||||
<van-icon :name="filters.isCollapsed ? 'arrow-down' : 'arrow-up'"
|
||||
:class="{ rotate: !filters.isCollapsed }"></van-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-search">
|
||||
<van-search v-model="filters.keyword" placeholder="搜索意见标题、内容或提交人"
|
||||
@search="onSearch"></van-search>
|
||||
</div>
|
||||
|
||||
<div class="filter-content" :class="{ hidden: filters.isCollapsed }">
|
||||
<div class="filter-row">
|
||||
<div class="filter-label">状态</div>
|
||||
<div class="filter-options">
|
||||
<div class="filter-tag" :class="{ active: filters.status === '' }" @click="setFilter('status', '')">
|
||||
全部
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.status === '0' }"
|
||||
@click="setFilter('status', '0')">
|
||||
待回复
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.status === '1' }"
|
||||
@click="setFilter('status', '1')">
|
||||
已回复
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row">
|
||||
<div class="filter-label">时间</div>
|
||||
<div class="filter-options">
|
||||
<div class="filter-tag" :class="{ active: filters.time === '' }" @click="setFilter('time', '')">全部
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.time === 'today' }"
|
||||
@click="setFilter('time', 'today')">今日
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.time === 'week' }"
|
||||
@click="setFilter('time', 'week')">
|
||||
本周
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.time === 'month' }"
|
||||
@click="setFilter('time', 'month')">本月
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="suggestion-list">
|
||||
<!-- 下拉刷新和上拉加载更多 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="loadMore"
|
||||
>
|
||||
<!-- 空状态 -->
|
||||
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
|
||||
<van-icon name="comment-circle-o" class="empty-icon"></van-icon>
|
||||
<div class="empty-text">暂无符合条件的意见</div>
|
||||
</div>
|
||||
|
||||
<!-- 意见列表 -->
|
||||
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id"
|
||||
@click="showDetail(item)">
|
||||
<div class="suggestion-header">
|
||||
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
|
||||
<div class="suggestion-status" :class="getStatusClass(item.isReply)">
|
||||
{{ getStatusText(item.isReply) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="suggestion-submitter">
|
||||
<van-icon name="contact"/>
|
||||
<span>{{ item.submitterName }}</span>
|
||||
<span class="suggestion-unit">{{ item.submitterUnitName }}</span>
|
||||
</div>
|
||||
<div class="suggestion-content">{{ item.content }}</div>
|
||||
<div class="suggestion-footer">
|
||||
<div class="suggestion-time">
|
||||
<van-icon name="clock-o"/>
|
||||
<span>{{ formatDate(item.submitTime) }}</span>
|
||||
</div>
|
||||
<div class="suggestion-action">
|
||||
{{ item.reply ? '查看详情' : '去回复' }}
|
||||
<van-icon name="arrow"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹出层 -->
|
||||
<van-popup v-model="showDetailPopup" round closeable position="bottom">
|
||||
<div class="detail-popup" v-if="currentSuggestion">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
|
||||
<div class="detail-meta">
|
||||
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
|
||||
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">提交人信息</div>
|
||||
<div class="submitter-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">姓名:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系方式:</span>
|
||||
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">意见内容</div>
|
||||
<div class="detail-content">{{ currentSuggestion.content }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section"
|
||||
v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
|
||||
<div class="detail-label">附件</div>
|
||||
<div class="detail-attachments">
|
||||
<div class="attachment-item" v-for="(file, idx) in currentSuggestion.attachments" :key="idx"
|
||||
@click.stop="previewImage(file.url, idx)">
|
||||
<img :src="file.url" class="attachment-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentSuggestion.reply">
|
||||
<div class="detail-label">已回复内容</div>
|
||||
<div class="detail-reply detail-content">{{ currentSuggestion.reply }}</div>
|
||||
<div class="detail-meta" style="margin-top: 10px;">
|
||||
回复时间:{{ formatDate(currentSuggestion.replyTime) }}
|
||||
</div>
|
||||
<div v-if="currentSuggestion.replyAttachments && currentSuggestion.replyAttachments.length > 0"
|
||||
style="margin-top: 12px;">
|
||||
<div class="detail-label">回复附件</div>
|
||||
<div class="detail-attachments">
|
||||
<div class="attachment-item" v-for="(file, idx) in currentSuggestion.replyAttachments"
|
||||
:key="idx" @click.stop="previewImage(file.url, idx, 'reply')">
|
||||
<img :src="file.url" class="attachment-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">{{ currentSuggestion.isReply ? '修改回复' : '回复意见' }}</div>
|
||||
<div class="reply-form">
|
||||
<textarea class="reply-textarea" v-model="currentSuggestion.replyContent"
|
||||
placeholder="请输入回复内容..."></textarea>
|
||||
|
||||
<!-- <div class="reply-attachments">-->
|
||||
<!-- <vant-file-upload :files.sync="formData.replyAttachments" :max="15"></vant-file-upload>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="reply-actions">
|
||||
<van-button style="border-radius: 10px" block type="info" :color="themeColor"
|
||||
@click="submitReply">提交回复
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
refreshing: false,
|
||||
loading: false,
|
||||
finished: false,
|
||||
showDetailPopup: false,
|
||||
currentSuggestion: null,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
suggestions: [],
|
||||
replyContent: '',
|
||||
replyAttachments: [],
|
||||
filters: {
|
||||
status: '', // 状态筛选
|
||||
time: '', // 时间筛选
|
||||
keyword: '', // 关键词搜索
|
||||
isCollapsed: true // 筛选区域是否折叠
|
||||
},
|
||||
stats: {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
replied: 0,
|
||||
today: 0
|
||||
},
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadStats();
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 加载统计数据
|
||||
loadStats() {
|
||||
$.post('/platform/suggestionBox/admin/getStats').done((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
this.stats = {
|
||||
total: res.data.total || 0,
|
||||
pending: res.data.pending || 0,
|
||||
replied: res.data.replied || 0,
|
||||
today: res.data.today || 0
|
||||
};
|
||||
}
|
||||
}).fail(() => {
|
||||
this.$toast.fail('统计数据加载失败');
|
||||
});
|
||||
},
|
||||
|
||||
// 加载意见数据
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
$.post('/platform/suggestionBox/admin/pageData', {
|
||||
pageNumber: this.pageNumber,
|
||||
pageSize: this.pageSize,
|
||||
status: this.filters.status,
|
||||
timeRange: this.filters.time,
|
||||
keyword: this.filters.keyword
|
||||
}).done((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
if (this.pageNumber === 1) {
|
||||
this.suggestions = res.data.list || [];
|
||||
} else {
|
||||
this.suggestions = this.suggestions.concat(res.data.list || []);
|
||||
}
|
||||
|
||||
this.finished = !res.data.list || res.data.list.length < this.pageSize;
|
||||
} else {
|
||||
this.finished = true;
|
||||
}
|
||||
this.loading = false;
|
||||
this.refreshing = false;
|
||||
}).fail(() => {
|
||||
this.loading = false;
|
||||
this.refreshing = false;
|
||||
this.finished = true;
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onRefresh() {
|
||||
this.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.loadStats();
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
loadMore() {
|
||||
this.pageNumber++;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 设置筛选条件
|
||||
setFilter(type, value) {
|
||||
this.filters[type] = value;
|
||||
this.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 搜索
|
||||
onSearch() {
|
||||
this.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 查看详情
|
||||
showDetail(suggestion) {
|
||||
if (suggestion.attachments && typeof suggestion.attachments === 'string') {
|
||||
try{
|
||||
suggestion.attachments = JSON.parse(suggestion.attachments);
|
||||
}catch (e){
|
||||
suggestion.attachments = [];
|
||||
}
|
||||
}
|
||||
console.log(suggestion)
|
||||
this.currentSuggestion = suggestion;
|
||||
this.showDetailPopup = true;
|
||||
},
|
||||
|
||||
// 提交回复
|
||||
submitReply() {
|
||||
if (!this.currentSuggestion.replyContent || !this.currentSuggestion.replyContent.trim()) {
|
||||
this.$toast('请输入回复内容');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$dialog.confirm({
|
||||
title: '确认提交',
|
||||
message: '确定提交此回复内容吗?'
|
||||
}).then(() => {
|
||||
// 提交回复
|
||||
$.post('/platform/suggestionBox/admin/reply', {
|
||||
reply: JSON.stringify(this.currentSuggestion)
|
||||
}).done((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success('回复成功');
|
||||
this.showDetailPopup = false;
|
||||
this.loadStats();
|
||||
this.pageData();
|
||||
} else {
|
||||
this.$toast.fail(res.msg || '回复失败');
|
||||
}
|
||||
}).fail(() => {
|
||||
this.$toast.fail('网络错误,请重试');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 获取状态class
|
||||
getStatusClass(isReply) {
|
||||
if (!isReply || isReply === 0) return 'status-pending';
|
||||
if (isReply === 1) return 'status-processing';
|
||||
return 'status-completed';
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(isReply) {
|
||||
if (!isReply || isReply === 0) return '待回复';
|
||||
if (isReply === 1) return '已回复';
|
||||
return '已处理';
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0');
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(url, index, type = 'submission') {
|
||||
if (!url) return;
|
||||
|
||||
// 创建图片查看器
|
||||
const urls = type === 'reply'
|
||||
? this.currentSuggestion.replyAttachments.map(file => file.url)
|
||||
: this.currentSuggestion.attachments.map(file => file.url);
|
||||
|
||||
this.$imagePreview({
|
||||
images: urls,
|
||||
startPosition: index
|
||||
});
|
||||
},
|
||||
|
||||
// 切换筛选区域折叠状态
|
||||
toggleFilterCollapse() {
|
||||
this.filters.isCollapsed = !this.filters.isCollapsed;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--#}#-->
|
||||
@@ -0,0 +1,538 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="意见箱" fixed placeholder></van-nav-bar>
|
||||
|
||||
<!-- 顶部Banner区域 -->
|
||||
<div class="banner-section">
|
||||
<div class="banner-content">
|
||||
<div class="banner-text">
|
||||
<h2>我们重视您的意见</h2>
|
||||
<p>每一条建议都将认真对待</p>
|
||||
</div>
|
||||
<div class="banner-image">
|
||||
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTI1LjYgMTAyLjdsLTE5LTE2LjhtLTQuNy0yMy43bDExLjUtNi44TTU3LjQgMTZsMTEuNSAxMS43TTMxLjIgNTMuOGwyMy42IDcuOSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtvcGFjaXR5Oi40Ii8+PHBhdGggZD0iTTY0LjEgMzEuN0w0NyA0OS45Yy0zIDMuMi0zLjEgOC4xLS4xIDExLjJsMjQuNCAyNWMzIDMuMSA3LjkgMy4yIDExIC4xTDk5IDY5YzMtMy4yIDMuMS04LjEuMS0xMS4yTDc0LjcgMzIuOGMtMyAzLjEtNy45IDMuMS0xMC42LTEuMXoiIHN0eWxlPSJmaWxsOiNmZmY7c3Ryb2tlOiNmZmY7c3Ryb2tlLW1pdGVybGltaXQ6MTAiLz48cGF0aCBkPSJNMTA4LjkgOTUuN2wtMTUuOC0xMi0xMi4yIDEzIDE0LjQgMTMuN2MuMyAyLjUgMi4zIDQuNSA0LjggNC41aDEzLjdjMi43IDAgNC45LTIuMiA0LjktNC45VjkzLjVjMC0yLjgtMi4xLTUtNC45LTV2OC40cy4xLTEuMi00LjkgMi44LjEtMyAuMS0zeiIgc3R5bGU9ImZpbGw6I2ZmZjtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMCIvPjwvc3ZnPg==" alt="Feedback">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能卡片区域 -->
|
||||
<div class="cards-container">
|
||||
<!-- 提交意见 -->
|
||||
<div class="feature-card" @click="goToSubmitPage">
|
||||
<div class="card-icon submit-icon">
|
||||
<van-icon name="edit" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3>提交意见</h3>
|
||||
<p>分享您的想法和建议</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的意见 -->
|
||||
<div class="feature-card" @click="goToMyOpinionsPage">
|
||||
<div class="card-icon my-icon">
|
||||
<van-icon name="records" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3>我的意见</h3>
|
||||
<p>查看您提交的所有意见</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 意见管理(管理员) -->
|
||||
<div class="feature-card" v-if="isAdmin" @click="goToAllOpinionsPage">
|
||||
<div class="card-icon admin-icon">
|
||||
<van-icon name="manager" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3>意见管理</h3>
|
||||
<p>管理所有用户提交的意见</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用指南 -->
|
||||
<div class="guide-container" v-if="!isAdmin">
|
||||
<div class="guide-header">
|
||||
<h3>使用指南</h3>
|
||||
</div>
|
||||
<div class="guide-steps">
|
||||
<div class="guide-step">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
<h4>提交意见</h4>
|
||||
<p>点击"提交意见"按钮,填写您的意见和建议</p>
|
||||
</div>
|
||||
<div class="step-icon">
|
||||
<van-icon name="edit" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<div class="guide-step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
<h4>等待处理</h4>
|
||||
<p>我们将在3个工作日内处理您的意见</p>
|
||||
</div>
|
||||
<div class="step-icon">
|
||||
<van-icon name="underway-o" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<div class="guide-step">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-content">
|
||||
<h4>查看回复</h4>
|
||||
<p>在"我的意见"中查看官方回复</p>
|
||||
</div>
|
||||
<div class="step-icon">
|
||||
<van-icon name="comment-o" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<div class="faq-container" v-if="!isAdmin">
|
||||
<div class="faq-header">
|
||||
<h3>常见问题</h3>
|
||||
<span class="faq-subtitle">解答您的疑惑</span>
|
||||
</div>
|
||||
<div class="faq-list">
|
||||
<div class="faq-item" :class="{'faq-active': activeFaq === 1}" @click="toggleFaq(1)">
|
||||
<div class="faq-question">
|
||||
<span class="faq-icon"><van-icon name="question-o" /></span>
|
||||
<span>如何提交带附件的意见?</span>
|
||||
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
|
||||
</div>
|
||||
<div class="faq-answer" v-show="activeFaq === 1">
|
||||
<p>在提交意见表单中,您可以上传最多3个文件作为附件,支持图片格式。点击附件上传区域,选择您要上传的文件即可。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item" :class="{'faq-active': activeFaq === 2}" @click="toggleFaq(2)">
|
||||
<div class="faq-question">
|
||||
<span class="faq-icon"><van-icon name="question-o" /></span>
|
||||
<span>意见提交后多久能收到回复?</span>
|
||||
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
|
||||
</div>
|
||||
<div class="faq-answer" v-show="activeFaq === 2">
|
||||
<p>我们会在3个工作日内处理您的意见,紧急问题会优先处理。您可以随时在"我的意见"中查看处理进度。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item" :class="{'faq-active': activeFaq === 3}" @click="toggleFaq(3)">
|
||||
<div class="faq-question">
|
||||
<span class="faq-icon"><van-icon name="question-o" /></span>
|
||||
<span>我可以修改已提交的意见吗?</span>
|
||||
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
|
||||
</div>
|
||||
<div class="faq-answer" v-show="activeFaq === 3">
|
||||
<p>提交后的意见暂不支持修改,如有补充,请重新提交并说明这是对之前意见的补充。我们会将相关意见关联处理。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(0, 78, 100);
|
||||
--primary-light: rgba(0, 78, 100, 0.1);
|
||||
--secondary-color: #37A6BD;
|
||||
--text-color: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-light: #999999;
|
||||
--bg-color: #f6f6f6;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #eeeeee;
|
||||
--success-color: #07c160;
|
||||
--warning-color: #ff976a;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* 顶部Banner */
|
||||
.banner-section {
|
||||
padding: 0;
|
||||
height: 180px;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.banner-curve {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 20px 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
color: white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.banner-text h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.banner-text p {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.banner-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* 功能卡片 */
|
||||
.cards-container {
|
||||
padding: 20px 16px 16px;
|
||||
margin-top: -20px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.card-icon .van-icon {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.submit-icon {
|
||||
background: linear-gradient(135deg, #1989fa 0%, #39b9f9 100%);
|
||||
}
|
||||
|
||||
.my-icon {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10d878 100%);
|
||||
}
|
||||
|
||||
.admin-icon {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ffaa7f 100%);
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-info h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.card-info p {
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* 使用指南 */
|
||||
.guide-container {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.guide-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.guide-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.guide-steps {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.guide-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-content h4 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
margin-left: 12px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.step-divider {
|
||||
height: 24px;
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
margin: 8px 0 8px 15px;
|
||||
}
|
||||
|
||||
/* 常见问题 */
|
||||
.faq-container {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.faq-header {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.faq-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.faq-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.faq-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-active .faq-question {
|
||||
background-color: var(--primary-light);
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
color: var(--primary-color);
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.faq-arrow {
|
||||
margin-left: auto;
|
||||
color: var(--text-light);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-active .faq-arrow .van-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
padding: 0 16px 16px 44px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
border-top: 1px dashed var(--border-color);
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.popup-title {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 16px;
|
||||
max-height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.suggestion-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.suggestion-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: function() {
|
||||
return {
|
||||
isAdmin: true, // 控制是否为管理员
|
||||
allSuggestions: [],
|
||||
user: 'user1',
|
||||
activeFaq: null,
|
||||
totalSuggestions: 0,
|
||||
respondedPercent: 0,
|
||||
recentSuggestions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userSuggestions: function() {
|
||||
return this.allSuggestions.filter(function(item) {
|
||||
return item.user === this.user;
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goToSubmitPage: function() {
|
||||
window.location.href = '/platform/suggestionBox/h5/write';
|
||||
},
|
||||
goToMyOpinionsPage: function() {
|
||||
window.location.href = '/platform/suggestionBox/h5/mine';
|
||||
},
|
||||
goToAllOpinionsPage: function() {
|
||||
window.location.href = '/platform/suggestionBox/admin/h5';
|
||||
},
|
||||
toggleFaq: function(id) {
|
||||
this.activeFaq = this.activeFaq === id ? null : id;
|
||||
},
|
||||
loadStats: function() {
|
||||
// 模拟加载统计数据
|
||||
this.totalSuggestions = 25;
|
||||
this.respondedPercent = 80;
|
||||
this.recentSuggestions = [1, 2, 3];
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
// 初始化数据
|
||||
if (this.isAdmin) {
|
||||
this.loadStats();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,542 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(0, 78, 100);
|
||||
--primary-light: rgba(0, 78, 100, 0.1);
|
||||
--secondary-color: #37a6bd;
|
||||
--text-color: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-light: #999999;
|
||||
--bg-color: #f5f7fa;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #eeeeee;
|
||||
--success-color: #07c160;
|
||||
--warning-color: #ff976a;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面样式 */
|
||||
.suggestion-list {
|
||||
padding: 16px;
|
||||
background-color: var(--bg-color);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.van-pull-refresh,
|
||||
.van-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suggestion-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.suggestion-card:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.suggestion-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.suggestion-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.suggestion-status {
|
||||
font-size: 12px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
max-height: 3.2em;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.suggestion-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
border-top: 1px solid #f5f5f5;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.suggestion-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suggestion-time .van-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.suggestion-action {
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.suggestion-action .van-icon {
|
||||
font-size: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 64px 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #ddd;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 15px;
|
||||
color: var(--text-light);
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.detail-popup {
|
||||
padding: 24px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-light);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
color: var(--text-color);
|
||||
line-height: 1.8;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-reply {
|
||||
background-color: #f9f9f9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.detail-attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.van-button--primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 下拉刷新和上拉加载样式 */
|
||||
.van-pull-refresh__track {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.van-list {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.no-reply {
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.no-reply-icon {
|
||||
font-size: 36px;
|
||||
color: #ccc;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-reply-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.submitter-info {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--text-secondary);
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="我的意见建议" left-arrow @click-left="history.go(-1)" fixed placeholder></van-nav-bar>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="suggestion-list">
|
||||
<!-- 下拉刷新和上拉加载更多 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="loadMore">
|
||||
<!-- 空状态 -->
|
||||
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
|
||||
<van-icon name="comment-circle-o" class="empty-icon" />
|
||||
<div class="empty-text">您还没有提交过意见</div>
|
||||
<van-button type="primary" size="normal" round @click="goToSubmitPage">去提交意见</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 意见列表 -->
|
||||
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id" @click="showDetail(item)">
|
||||
<div class="suggestion-header">
|
||||
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
|
||||
<div class="suggestion-status" :class="getStatusClass(item.isReply)">{{ item.node_name }}</div>
|
||||
</div>
|
||||
<div class="suggestion-content">{{ item.content }}</div>
|
||||
<div class="suggestion-footer">
|
||||
<div class="suggestion-time">
|
||||
<van-icon name="clock-o" />
|
||||
<span>{{ formatDate(item.submitTime) }}</span>
|
||||
</div>
|
||||
<div style="display: flex; column-gap: 10px">
|
||||
<div class="suggestion-action">
|
||||
查看详情
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
<div class="suggestion-action" v-if="item.flow_status==='1'" @click.stop="onRevoke(item)">
|
||||
撤销
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹出层 -->
|
||||
<van-popup v-model="showDetailPopup" round closeable position="bottom" :style="{ height: '80%' }">
|
||||
<div class="detail-popup" v-if="currentSuggestion">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
|
||||
<div class="detail-meta">
|
||||
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
|
||||
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">提交人信息</div>
|
||||
<div class="submitter-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">姓名:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系方式:</span>
|
||||
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">意见内容</div>
|
||||
<div class="detail-content">{{ currentSuggestion.content }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
|
||||
<div class="detail-label">附件</div>
|
||||
<div class="detail-attachments">
|
||||
<div
|
||||
class="attachment-item"
|
||||
v-for="(file, idx) in currentSuggestion.attachments"
|
||||
:key="idx"
|
||||
@click.stop="previewImage(file.url, idx)"
|
||||
>
|
||||
<img :src="file.url" class="attachment-image" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentSuggestion.isReply">
|
||||
<div class="detail-label">回复</div>
|
||||
<div class="detail-reply detail-content">{{ currentSuggestion.replyContent }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="detail-section">
|
||||
<div class="detail-label">回复</div>
|
||||
<div class="no-reply">
|
||||
<van-icon name="chat-o" class="no-reply-icon" />
|
||||
<div class="no-reply-text">暂无回复,请耐心等待</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
refreshing: false,
|
||||
loading: false,
|
||||
finished: false,
|
||||
showDetailPopup: false,
|
||||
currentSuggestion: null,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
suggestions: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.loading = true
|
||||
$.post("/platform/suggestionBox/pageData", {
|
||||
pageNumber: this.pageNumber,
|
||||
pageSize: this.pageSize
|
||||
})
|
||||
.done((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
if (this.pageNumber === 1) {
|
||||
this.suggestions = res.data.list || []
|
||||
} else {
|
||||
this.suggestions = this.suggestions.concat(res.data.list || [])
|
||||
}
|
||||
|
||||
this.finished = !res.data.list || res.data.list.length < this.pageSize
|
||||
} else {
|
||||
this.finished = true
|
||||
}
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
this.finished = true
|
||||
})
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onRefresh() {
|
||||
this.pageNumber = 1
|
||||
this.finished = false
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
loadMore() {
|
||||
this.pageNumber++
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 查看详情
|
||||
showDetail(suggestion) {
|
||||
if (suggestion.attachments && typeof suggestion.attachments === "string") {
|
||||
try {
|
||||
suggestion.attachments = JSON.parse(suggestion.attachments)
|
||||
} catch (e) {
|
||||
suggestion.attachments = []
|
||||
}
|
||||
}
|
||||
this.currentSuggestion = suggestion
|
||||
this.showDetailPopup = true
|
||||
},
|
||||
|
||||
// 获取状态class
|
||||
getStatusClass(isReply) {
|
||||
if (!isReply || isReply === 0) return "status-pending"
|
||||
if (isReply === 1) return "status-processing"
|
||||
return "status-completed"
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(isReply) {
|
||||
if (!isReply || isReply === 0) return "待处理"
|
||||
if (isReply === 1) return "处理中"
|
||||
return "已处理"
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ""
|
||||
const date = new Date(dateStr)
|
||||
return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0")
|
||||
},
|
||||
|
||||
// 前往提交意见页面
|
||||
goToSubmitPage() {
|
||||
window.location.href = "/platform/suggestionBox/h5/write"
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(url, index) {
|
||||
if (!url) return
|
||||
|
||||
// 创建图片查看器
|
||||
const urls = this.currentSuggestion.attachments.map((file) => file.url)
|
||||
this.$imagePreview({
|
||||
images: urls,
|
||||
startPosition: index
|
||||
})
|
||||
},
|
||||
|
||||
// 撤销
|
||||
onRevoke(item) {
|
||||
this.$dialog
|
||||
.confirm({
|
||||
title: "提示",
|
||||
message: "您确认要撤销吗?"
|
||||
})
|
||||
.then(() => {})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#}#-->
|
||||
@@ -0,0 +1,363 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.task-form-container {
|
||||
padding: 16px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background-color: rgb(0, 78, 100);
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
background-color: #f9f9f9;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.reply-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.reply-textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="意见箱处理" left-arrow @click-left="goBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="task-form-container">
|
||||
<!-- 加载状态 -->
|
||||
<van-empty v-if="loading" description="加载中...">
|
||||
<template #image>
|
||||
<van-loading type="spinner" size="36" />
|
||||
</template>
|
||||
</van-empty>
|
||||
|
||||
<template v-else>
|
||||
<!-- 业务表单数据 -->
|
||||
<div class="card">
|
||||
<div class="card-title">基本信息</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">提交人:</span>
|
||||
<span class="info-value">{{ suggestionData.submitterName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">{{ suggestionData.submitterLoginName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">{{ suggestionData.submitterUnitName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系方式:</span>
|
||||
<span class="info-value">{{ suggestionData.concat }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">提交时间:</span>
|
||||
<span class="info-value">{{ formatDate(suggestionData.submitTime) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">标题:</span>
|
||||
<span class="info-value">{{ suggestionData.title || '无标题' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">内容:</span>
|
||||
</div>
|
||||
<div class="content-box">{{ suggestionData.content }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程信息 -->
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
流程信息
|
||||
<span class="status-tag" :class="getStatusClass()">{{ getStatusText() }}</span>
|
||||
</div>
|
||||
<div class="timeline">
|
||||
<van-steps direction="vertical" :active="flowHistory.length - 1">
|
||||
<van-step v-for="(item, index) in flowHistory" :key="index">
|
||||
<h3>{{ item.taskName }}</h3>
|
||||
<p>处理人: {{ item.assigneeName || '系统' }}</p>
|
||||
<p>开始时间: {{ formatDate(item.startTime) }}</p>
|
||||
<p v-if="item.endTime">完成时间: {{ formatDate(item.endTime) }}</p>
|
||||
<p v-if="item.comment">处理意见: {{ item.comment }}</p>
|
||||
</van-step>
|
||||
</van-steps>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 回复表单 -->
|
||||
<div class="card" v-if="!taskCompleted && currentTask">
|
||||
<div class="card-title">处理意见</div>
|
||||
<div class="reply-form">
|
||||
<textarea v-model="replyForm.replyContent" class="reply-textarea"
|
||||
placeholder="请输入回复内容"></textarea>
|
||||
<div class="btn-container">
|
||||
<van-button type="primary" block round @click="submitReply">提交回复</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已回复信息 -->
|
||||
<div class="card" v-if="suggestionData.isReply">
|
||||
<div class="card-title">回复信息</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">回复人:</span>
|
||||
<span class="info-value">{{ suggestionData.replyUserName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">回复时间:</span>
|
||||
<span class="info-value">{{ formatDate(suggestionData.replyTime) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">回复内容:</span>
|
||||
</div>
|
||||
<div class="content-box">{{ suggestionData.replyContent }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
taskId: "",
|
||||
businessKey: "",
|
||||
processInstanceId: "",
|
||||
suggestionData: {},
|
||||
flowHistory: [],
|
||||
currentTask: null,
|
||||
taskCompleted: false,
|
||||
replyForm: {
|
||||
id: "",
|
||||
replyContent: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取URL参数
|
||||
this.taskId = GetQueryString("taskId")
|
||||
this.businessKey = GetQueryString("businessKey")
|
||||
this.processInstanceId = GetQueryString("processInstanceId")
|
||||
|
||||
// 加载数据
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
history.back()
|
||||
},
|
||||
|
||||
// 加载数据
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
// 1. 获取业务数据
|
||||
await this.loadSuggestionData()
|
||||
|
||||
// 2. 获取流程历史
|
||||
await this.loadFlowHistory()
|
||||
|
||||
// 3. 获取当前任务信息
|
||||
if (this.taskId) {
|
||||
await this.loadTaskInfo()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载数据失败:", error)
|
||||
this.$toast.fail("加载数据失败")
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 加载意见箱数据
|
||||
async loadSuggestionData() {
|
||||
if (!this.businessKey) return
|
||||
|
||||
const {
|
||||
code,
|
||||
data
|
||||
} = await $.post("/platform/suggestionBox/admin/getSuggestionById", { id: this.businessKey })
|
||||
if (code === 0 && data) {
|
||||
this.suggestionData = data
|
||||
this.replyForm.id = data.id
|
||||
}
|
||||
},
|
||||
|
||||
// 加载流程历史
|
||||
async loadFlowHistory() {
|
||||
if (!this.processInstanceId) return
|
||||
|
||||
const {
|
||||
code,
|
||||
data
|
||||
} = await $.post("/platform/workflow/task/getProcessHistory", { processInstanceId: this.processInstanceId })
|
||||
if (code === 0 && data) {
|
||||
this.flowHistory = data
|
||||
}
|
||||
},
|
||||
|
||||
// 加载任务信息
|
||||
async loadTaskInfo() {
|
||||
const { code, data } = await $.post("/platform/workflow/task/getTaskInfo", { taskId: this.taskId })
|
||||
if (code === 0 && data) {
|
||||
this.currentTask = data
|
||||
this.taskCompleted = data.endTime != null
|
||||
}
|
||||
},
|
||||
|
||||
// 提交回复
|
||||
async submitReply() {
|
||||
if (!this.replyForm.replyContent.trim()) {
|
||||
this.$toast("请输入回复内容")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存业务数据并完成任务
|
||||
const result = await $.post("/platform/suggestionBox/admin/reply", {
|
||||
reply: JSON.stringify(this.replyForm),
|
||||
taskId: this.taskId
|
||||
})
|
||||
|
||||
if (result.code === 0) {
|
||||
this.$toast.success("处理成功")
|
||||
// 重新加载数据
|
||||
setTimeout(() => {
|
||||
this.loadData()
|
||||
}, 1000)
|
||||
} else {
|
||||
this.$toast.fail(result.msg || "处理失败")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("提交回复失败:", error)
|
||||
this.$toast.fail("提交回复失败")
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态样式
|
||||
getStatusClass() {
|
||||
if (this.flowHistory.length === 0) return "status-pending"
|
||||
|
||||
const lastTask = this.flowHistory[this.flowHistory.length - 1]
|
||||
if (!lastTask.endTime) return "status-processing"
|
||||
return "status-completed"
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText() {
|
||||
if (this.flowHistory.length === 0) return "未开始"
|
||||
|
||||
const lastTask = this.flowHistory[this.flowHistory.length - 1]
|
||||
if (!lastTask.endTime) return "处理中"
|
||||
return "已完成"
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ""
|
||||
const date = new Date(dateStr)
|
||||
return date.getFullYear() + "-" +
|
||||
String(date.getMonth() + 1).padStart(2, "0") + "-" +
|
||||
String(date.getDate()).padStart(2, "0") + " " +
|
||||
String(date.getHours()).padStart(2, "0") + ":" +
|
||||
String(date.getMinutes()).padStart(2, "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,294 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
background-color: #f6f6f6;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: rgb(0, 78, 100);
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.section-title span {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
width: 80px;
|
||||
color: #333;
|
||||
padding: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
.input-control {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.input-control input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: right;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.textarea-container {
|
||||
padding: 10px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.textarea-container textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.word-count {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.upload-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.upload-item,
|
||||
.upload-btn {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uploaded-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
|
||||
.submit-area {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-sticky>
|
||||
<van-nav-bar title="我要提意见" left-arrow left-text="返回" @click-left="history.go(-1)"></van-nav-bar>
|
||||
</van-sticky>
|
||||
|
||||
<div class="form-container">
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>填写人信息</span>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">姓名</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.submitterName" placeholder="请输入姓名" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">工号</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.submitterLoginName" placeholder="请输入工号" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">手机号码</div>
|
||||
<div class="input-control">
|
||||
<input type="tel" v-model="formData.concat" placeholder="请输入手机号码" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">所在单位</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.submitterUnitName" placeholder="请输入所在单位" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>填写标题</span>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">标题</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.title" placeholder="请输入意见标题" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>填写意见建议内容</span>
|
||||
</div>
|
||||
<div class="textarea-container">
|
||||
<textarea v-model="formData.content" placeholder="请描述您要填写的意见建议内容..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>照片上传</span>
|
||||
</div>
|
||||
|
||||
<div class="upload-area">
|
||||
<!-- <vant-file-upload :files.sync="formData.attachments" :max="15"></vant-file-upload>-->
|
||||
<!-- <div class="upload-grid">-->
|
||||
<!-- <div class="upload-item" v-for="(item, index) in formData.fileList" :key="index">-->
|
||||
<!-- <img :src="item.content || item.url" class="uploaded-image">-->
|
||||
<!-- <div class="delete-btn" @click="deleteImage(index)">-->
|
||||
<!-- <van-icon name="cross" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="upload-btn" v-if="formData.fileList.length < 3" @click="triggerUpload">-->
|
||||
<!-- <input type="file" ref="fileInput" style="display:none" accept="image/*" @change="onFileChange" multiple>-->
|
||||
<!-- <van-icon name="plus" size="24" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-area">
|
||||
<van-button @click="submitForm" style="border-radius: 10px" block type="info">提 交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
name: "",
|
||||
mobile: "",
|
||||
idCard: "",
|
||||
department: "",
|
||||
content: "",
|
||||
address: "",
|
||||
fileList: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
history.back()
|
||||
},
|
||||
|
||||
async submitForm() {
|
||||
if (!this.formData.concat.trim()) {
|
||||
this.$toast("请输入手机号码")
|
||||
return
|
||||
}
|
||||
//正则验证
|
||||
if (!/^1[3-9]\d{9}$/.test(this.formData.concat)) {
|
||||
this.$toast("请输入正确的手机号码")
|
||||
return
|
||||
}
|
||||
if (!this.formData.content.trim()) {
|
||||
this.$toast("请输入投诉内容")
|
||||
return
|
||||
}
|
||||
this.$dialog
|
||||
.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
})
|
||||
.then(async () => {
|
||||
const { code, data, msg } = await $.post("/platform/suggestionBox/submit", { suggestion: JSON.stringify(this.formData) })
|
||||
if (code === 0) {
|
||||
this.$toast.success("提交成功")
|
||||
setTimeout(() => {
|
||||
// history.back()
|
||||
}, 200)
|
||||
} else {
|
||||
this.$toast(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const id = GetQueryString("id")
|
||||
if (!id) {
|
||||
this.$set(this.formData, "submitterId", "${@auth.getPrincipalProperty('id')}")
|
||||
this.$set(this.formData, "submitterName", "${@auth.getPrincipalProperty('username')}")
|
||||
this.$set(this.formData, "submitterLoginName", "${@auth.getPrincipalProperty('loginname')}")
|
||||
this.$set(this.formData, "submitterUnitId", "${@auth.getPrincipalProperty('unitid')}")
|
||||
this.$set(this.formData, "submitterUnitName", "${@auth.getPrincipalProperty('unit').getName()}")
|
||||
this.$set(this.formData, "submitterUnionId", "${@auth.getPrincipalProperty('union').getId()}")
|
||||
this.$set(this.formData, "submitterUnionName", "${@auth.getPrincipalProperty('union').getName()}")
|
||||
this.$set(this.formData, "concat", "${@auth.getPrincipalProperty('mobile')}")
|
||||
console.log(this.formData)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#}#-->
|
||||
Reference in New Issue
Block a user