初始代码

This commit is contained in:
wangmingwei
2026-04-21 17:48:26 +08:00
parent d3631949e9
commit 7f9e424a5c
1822 changed files with 288292 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
defaultFullscreen
:footer="null"
:closable="false"
:keyboard="false"
class="yunzhupaas-full-modal full-modal report-modal designer-modal">
<iframe :src="state.url" width="100%" height="100%" frameborder="0" class="frame" />
</BasicModal>
</template>
<script lang="ts" setup>
import { reactive, onMounted } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { useGlobSetting } from '@/hooks/setting';
import { getToken } from '@/utils/auth';
interface State {
url: string;
}
const emit = defineEmits(['register', 'reload']);
const { report } = useGlobSetting();
const [registerModal, { closeModal }] = useModalInner(init);
const state = reactive<State>({
url: '',
});
function init(data) {
state.url = `${report}/index.html?token=${getToken()}${data.id ? '&id=' + data.id : ''}`;
}
function handleMessage(e) {
const data = e.data;
if (data !== 'closeDialog') return;
state.url = '';
closeModal();
emit('reload');
}
onMounted(() => {
window.addEventListener('message', handleMessage);
});
</script>
<style lang="less">
.report-modal {
.ant-modal-header {
display: none !important;
}
.scrollbar {
padding: 0 !important;
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" class="full-popup report-popup">
<iframe :src="state.url" width="100%" height="100%" frameborder="0" />
</BasicPopup>
</template>
<script lang="ts" setup>
import { reactive, onMounted } from 'vue';
import { BasicPopup, usePopupInner } from '@/components/Popup';
import { useGlobSetting } from '@/hooks/setting';
import { getToken } from '@/utils/auth';
import { getDataReportInfo } from '@/api/onlineDev/dataReport';
interface State {
url: string;
}
defineEmits(['register']);
const { report } = useGlobSetting();
const [registerPopup, { closePopup }] = usePopupInner(init);
const state = reactive<State>({
url: '',
});
function init(data) {
let targetUrl = `${report}/preview.html?id=${data.id}&token=${getToken()}&page=1`;
getDataReportInfo(data.id).then(res => {
let item = {};
if (res.data?.searchForm?.components && Array.isArray(res.data.searchForm.components)) {
listQuery(res.data.searchForm.components, item);
for (let key in item) {
let item1 = '&' + key + '=' + item[key];
targetUrl += item1;
}
}
state.url = targetUrl;
});
}
function listQuery(list, callback) {
for (let i = 0; i < list.length; i++) {
let item = list[i];
let arrayList = [];
if (item.hasOwnProperty('cols') && Array.isArray(item.cols)) {
arrayList = arrayList.concat(item.cols);
}
if (item.hasOwnProperty('children') && Array.isArray(item.children)) {
arrayList = arrayList.concat(item.children);
}
if (item.bindParameter && item.defaultValue) {
callback[item.bindParameter] = item.defaultValue;
}
listQuery(arrayList, callback);
}
}
function handleMessage(e) {
const data = e.data;
if (data !== 'closeDialog') return;
state.url = '';
closePopup();
}
onMounted(() => {
window.addEventListener('message', handleMessage);
});
</script>
<style lang="less">
.report-popup {
.yunzhupaas-basic-popup-header {
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ t('common.addText') }}</a-button>
<yunzhupaas-upload-btn :url="reportServer + '/Data/Actions/Import'" accept=".json" @on-success="reload"></yunzhupaas-upload-btn>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'enabledMark'">
<a-tag :color="record.enabledMark === 1 ? 'success' : 'error'">{{ record.enabledMark == 1 ? '启用' : '禁用' }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<PreviewModal @register="registerPreview" type="flow" @previewPc="previewPc" />
<Form @register="registerForm" @reload="reload" />
<PreviewPopup @register="registerPreviewPopup" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getDataReportList, delDataReport, copy, release } from '@/api/onlineDev/dataReport';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useModal } from '@/components/Modal';
import { usePopup } from '@/components/Popup';
import { useBaseStore } from '@/store/modules/base';
import { downloadByUrl } from '@/utils/file/download';
import { useGlobSetting } from '@/hooks/setting';
import { getToken } from '@/utils/auth';
import { PreviewModal } from '@/components/CommonModal';
import Form from './Form.vue';
import PreviewPopup from './PreviewPopup.vue';
defineOptions({ name: 'onlineDev-dataReport' });
const { createMessage } = useMessage();
const baseStore = useBaseStore();
const { t } = useI18n();
const [registerPreview, { openModal: openPreviewModal }] = useModal();
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerPreviewPopup, { openPopup: openPreviewPopup }] = usePopup();
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName', width: 200 },
{ title: '编码', dataIndex: 'enCode', width: 200 },
{ title: '分类', dataIndex: 'category', width: 150 },
{ title: '创建人', dataIndex: 'creatorUser', width: 120 },
{ title: '创建时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '最后修改时间', dataIndex: 'lastModifyTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '排序', dataIndex: 'sortCode', width: 70, align: 'center' },
{ title: '状态', dataIndex: 'enabledMark', width: 70, align: 'center' },
];
const { reportServer } = useGlobSetting();
const currRow = ref<any>({});
const categoryList = ref<any[]>([]);
const [registerTable, { reload, getForm }] = useTable({
api: getDataReportList,
columns,
useSearchForm: true,
immediate: false,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: {
placeholder: '请选择',
showSearch: true,
},
},
{
field: 'enabledMark',
label: '状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '启用', id: 1 },
{ fullName: '禁用', id: 0 },
],
},
},
],
},
actionColumn: {
width: 150,
title: '操作',
dataIndex: 'action',
},
afterFetch: data => {
const list = data.map(o => {
let category = '';
const arr = categoryList.value.filter(category => category.id == o.categoryId);
if (arr.length) {
const item = arr[0];
category = item && item.fullName ? item.fullName : '';
}
return { ...o, category };
});
return list;
},
});
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText'),
onClick: addOrUpdateHandle.bind(null, record.id),
},
{
label: t('common.delText'),
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function getDropDownActions(record): ActionItem[] {
return [
{
label: t('common.previewText'),
onClick: handlePreview.bind(null, record),
},
{
label: t('common.copyText'),
modelConfirm: {
content: '您确定要复制该报表, 是否继续?',
onOk: handleCopy.bind(null, record.id),
},
},
{
label: t('common.exportText'),
modelConfirm: {
content: '您确定要导出该报表, 是否继续?',
onOk: handleExport.bind(null, record.id),
},
},
{
ifShow: !record.enabledMark,
label: '启用',
modelConfirm: {
content: '此操作将启用该报表,是否继续?',
onOk: handleRelease.bind(null, record),
},
},
{
ifShow: !!record.enabledMark,
label: '禁用',
modelConfirm: {
content: '此操作将禁用该报表,是否继续?',
onOk: handleRelease.bind(null, record),
},
},
];
}
function addOrUpdateHandle(id = '') {
openFormModal(true, { id });
}
function handlePreview(record) {
currRow.value = record;
openPreviewModal(true, { type: 'report', id: record.id, fullName: record.fullName });
}
function previewPc() {
openPreviewPopup(true, { id: currRow.value.id });
}
function handleDelete(id) {
delDataReport(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleCopy(id) {
copy(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleExport(id) {
const token = getToken();
const url = `${reportServer}/Data/${id}/Actions/Export?token=${token}`;
downloadByUrl({ url });
}
async function getOptions() {
const res = await baseStore.getDictionaryData('businessType');
categoryList.value = res as any[];
getForm().updateSchema({ field: 'category', componentProps: { options: res } });
reload();
}
function handleRelease(record) {
release(record.id).then(res => {
createMessage.success(res.msg);
reload();
});
}
onMounted(() => {
getOptions();
});
</script>

View File

@@ -0,0 +1,120 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="dataForm.id ? t('common.editText') : t('common.addText')" @ok="handleSubmit">
<BasicForm @register="registerForm" />
<template #appendFooter>
<a-button type="primary" :loading="btnLoading" @click="handleSubmit(1)">确定并设计</a-button>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { toRefs, reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import formValidate from '@/utils/formValidate';
import { getInfo, update, create } from '@/api/onlineDev/integrate';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
dataForm: any;
btnLoading: boolean;
}
const state = reactive<State>({
dataForm: { id: '' },
btnLoading: false,
});
const { dataForm, btnLoading } = toRefs(state);
const { createMessage } = useMessage();
const { t } = useI18n();
const emit = defineEmits(['register', 'reload', 'design']);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 60,
schemas: [
{
field: 'fullName',
label: '名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 100 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
},
{
field: 'type',
label: '类型',
component: 'Select',
componentProps: {
options: [
{ id: 1, fullName: '事件触发' },
{ id: 2, fullName: '定时触发' },
{ id: 3, fullName: 'webhook触发' },
],
disabled: true,
},
rules: [{ required: true, trigger: 'change', message: '必填', type: 'number' }],
},
{
field: 'enabledMark',
label: '状态',
component: 'Switch',
defaultValue: 1,
},
{
field: 'description',
label: '说明 ',
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
],
});
function init(data) {
resetFields();
state.dataForm.id = data.id || '';
state.dataForm.type = data.type || 1;
if (state.dataForm.id) {
changeLoading(true);
getInfo(state.dataForm.id)
.then(res => {
state.dataForm = res.data;
setFieldsValue(state.dataForm);
changeLoading(false);
})
.catch(() => {
changeLoading(false);
});
} else {
setFieldsValue({ type: state.dataForm.type });
}
}
async function handleSubmit(type?) {
const values = await validate();
if (!values) return;
type === 1 ? (state.btnLoading = true) : changeOkLoading(true);
const query = { ...values, id: state.dataForm.id };
const formMethod = state.dataForm.id ? update : create;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
state.btnLoading = false;
closeModal();
emit('reload');
if (type === 1) emit('design', state.dataForm.id || res.data);
})
.catch(() => {
changeOkLoading(false);
state.btnLoading = false;
});
}
</script>

View File

@@ -0,0 +1,29 @@
<template>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" title="执行队列" width="600px" class="full-drawer" destroy-on-close>
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'enabledMark'">
<a-tag :color="record.state === 1 ? 'success' : ''">{{ record.state === 1 ? '执行中' : '等待' }}</a-tag>
</template>
</template>
</BasicTable>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { getQueueList } from '@/api/onlineDev/integrate';
import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
import { BasicTable, useTable, BasicColumn } from '@/components/Table';
const [registerDrawer, {}] = useDrawerInner();
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName' },
{ title: '执行时间', dataIndex: 'executionTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '状态', dataIndex: 'enabledMark', width: 70 },
];
const [registerTable] = useTable({
api: getQueueList,
columns,
pagination: false,
showTableSetting: false,
});
</script>

View File

@@ -0,0 +1,157 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :title="getTitle" class="full-popup">
<BasicTable :columns="columns" @register="registerTable" class="yunzhupaas-sub-table yunzhupaas-sub-table-full" v-show="!showFlow">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'processId'">
{{ record.processId }}
<template v-if="record.isRetry">
<a-tag color="blue" class="!mx-8px">重试</a-tag>
<BasicHelp :text="[`原实例ID${record.parentId}`, `原实例执行时间:${dayjs(record.parentTime).format('YYYY-MM-DD HH:mm:ss')}`]">
<template #default><i class="icon-ym icon-ym-generator-link" /></template>
</BasicHelp>
</template>
</template>
<template v-if="column.key === 'enabledMark'">
<a-tag :color="record.resultType === 1 ? 'success' : 'error'">{{ record.resultType === 1 ? '成功' : '失败' }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
</BasicTable>
<LogDetail @register="registerLogDetailModal" @updateNodes="updateNodes" />
</BasicPopup>
</template>
<script lang="ts" setup>
import { getTaskList, retryTask, delTask } from '@/api/onlineDev/integrate';
import { ref, unref, computed, reactive } from 'vue';
import { BasicPopup, usePopupInner } from '@/components/Popup';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import { useModal } from '@/components/Modal';
import LogDetail from './LogDetail.vue';
import dayjs from 'dayjs';
const { createMessage } = useMessage();
const { t } = useI18n();
const [registerPopup, { closePopup }] = usePopupInner(init);
const emit = defineEmits(['register', 'updateNodes']);
const title = ref('');
const showFlow = ref(false);
const columns: BasicColumn[] = [
{ title: '实例ID', dataIndex: 'processId' },
{ title: '执行结果', dataIndex: 'enabledMark', width: 150 },
{ title: '执行时间', dataIndex: 'executionTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
];
const searchInfo = reactive({ integrateId: '' });
const [registerLogDetailModal, { openModal: openLogDetailModal }] = useModal();
const [registerTable, { reload }] = useTable({
api: getTaskList,
searchInfo: searchInfo,
useSearchForm: true,
formConfig: {
baseColProps: { span: 6 },
schemas: [
{
field: 'pickerVal',
label: '执行时间',
component: 'DateRange',
componentProps: {
format: 'YYYY-MM-DD HH:mm:ss',
showTime: { defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')] },
placeholder: ['开始时间', '结束时间'],
},
},
{
field: 'resultType',
label: '执行结果',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '成功', id: 1 },
{ fullName: '失败', id: 0 },
],
showSearch: true,
allowClear: false,
},
},
],
fieldMapToTime: [['pickerVal', ['startTime', 'endTime']]],
},
immediate: false,
tableSetting: { setting: false, redo: false },
actionColumn: {
width: 150,
title: '操作',
dataIndex: 'action',
},
});
const getTitle = computed(() => (unref(showFlow) ? '流程图' : unref(title)));
function init(data) {
title.value = data.fullName;
searchInfo.integrateId = data.id;
reload();
}
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.detailText'),
onClick: handleDetail.bind(null, record.id),
},
{
label: t('common.delText'),
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
{
label: '重试',
ifShow: record.resultType != 1,
modelConfirm: {
content: '确定将本实例进行重试,是否继续?',
onOk: handleRedo.bind(null, record.id),
},
},
];
}
function handleDetail(id) {
openLogDetailModal(true, { id });
}
function handleRedo(id) {
retryTask(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDelete(id) {
delTask(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function updateNodes() {
emit('updateNodes', searchInfo.integrateId);
closePopup();
}
</script>
<style lang="less" scoped>
.process-preview {
height: 100%;
padding: 10px 0;
:deep(.process-flow-container) {
.tips {
display: none;
}
.scale-slider {
right: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,199 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="日志详情" width="800px" :footer="null" class="yunzhupaas-log-detail-modal">
<div class="log-detail-board">
<div class="left-board">
<div class="item-box" :class="{ active: activeItem.id == item.id }" v-for="(item, index) in list" @click="handleClick(item, index)">
<div class="top">
<span class="fullName">{{ item.nodeName }}</span>
<i class="icon icon-ym icon-ym-success" v-if="item.resultType" />
<i class="icon icon-ym icon-ym-fail" v-else />
</div>
<div class="bottom">
<i class="icon-ym icon-ym-clock pr-4px" v-if="item.type == 1" />
<i class="icon-ym icon-ym-btn-refresh pr-4px" v-else />
{{ dayjs(item.endTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
</div>
<div class="center-board">
<div class="top">
<span>{{ activeItem.nodeName }}</span>
<a-space v-if="!activeItem.resultType">
<a-button @click="handleUpdateNodes">去修改节点</a-button>
<a-button @click="handleRedoNodes" :disabled="!activeItem.isRetry" :loading="redoNodesLoading">
{{ redoNodesLoading ? '正在修复中...' : '节点重试' }}
</a-button>
</a-space>
</div>
<div class="time-box">
<span>开始时间{{ dayjs(activeItem.startTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
<span>结束时间{{ dayjs(activeItem.endTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div>
<a-radio-group class="!mb-10px" v-model:value="activeKey" button-style="solid">
<a-radio-button :value="1">输入</a-radio-button>
<a-radio-button :value="2" v-if="!activeItem.resultType">错误</a-radio-button>
</a-radio-group>
<a-textarea v-model:value="getTextareaValue" :rows="17" readOnly />
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { getTaskInfo, nodeRetryTask } from '@/api/onlineDev/integrate';
import { BasicModal, useModalInner } from '@/components/Modal';
import { reactive, toRefs, computed } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import dayjs from 'dayjs';
interface State {
list: any[];
activeKey: number;
activeIndex: number;
activeItem: any;
id: string;
msgInfo: string;
redoNodesLoading: boolean;
}
const emit = defineEmits(['register', 'updateNodes']);
const { t } = useI18n();
const { createMessage, createConfirm } = useMessage();
const [registerModal, { closeModal, changeLoading }] = useModalInner(init);
const state = reactive<State>({
list: [],
activeKey: 1,
activeIndex: 0,
activeItem: {},
id: '',
msgInfo: '',
redoNodesLoading: false,
});
const { list, activeKey, activeItem, redoNodesLoading } = toRefs(state);
const getTextareaValue = computed(() => (state.activeKey === 1 ? state.msgInfo : state.activeItem.errorMsg));
function init(data) {
state.activeKey = 1;
state.list = [];
state.activeItem = {};
state.id = data.id;
initData(true);
}
function initData(isInit?) {
changeLoading(true);
getTaskInfo(state.id).then(res => {
changeLoading(false);
state.list = res.data.list || [];
state.msgInfo = res.data && res.data.data;
if (state.list.length) state.activeItem = state.list[isInit ? 0 : state.activeIndex];
});
}
function handleClick(item, index) {
state.activeItem = item;
state.activeIndex = index;
state.activeKey = 1;
}
function handleUpdateNodes() {
emit('updateNodes');
closeModal();
}
function handleRedoNodes() {
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '确定将本节点进行重试?',
onOk: () => {
const query = {
id: state.id,
nodeId: state.activeItem.id,
};
state.redoNodesLoading = true;
nodeRetryTask(query)
.then(res => {
state.redoNodesLoading = false;
createMessage.success(res.msg);
initData();
})
.catch(() => {
state.redoNodesLoading = false;
});
},
});
}
</script>
<style lang="less">
.yunzhupaas-log-detail-modal {
.log-detail-board {
display: flex;
height: 550px;
.left-board {
width: 270px;
height: 100%;
overflow-y: auto;
border-right: 1px solid @border-color-base;
.active {
background-color: @tree-node-selected-bg !important;
}
.item-box {
padding: 8px 20px;
cursor: pointer;
&:hover {
background-color: @selected-hover-bg;
}
.top {
display: flex;
align-items: center;
.fullName {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}
.icon {
width: 28px;
height: 28px;
border-radius: 50%;
padding: 2px;
text-align: center;
color: #fff;
transform: scale(0.65);
}
.icon-ym-fail {
background-color: #ff4d4d;
}
.icon-ym-success {
background-color: #55d187;
}
}
.bottom {
display: flex;
align-items: center;
.icon-ym-btn-refresh {
color: @primary-color;
}
}
}
}
.center-board {
width: 100%;
padding: 15px 20px;
.top {
display: flex;
align-items: center;
justify-content: space-between;
}
.time-box {
padding: 20px 0;
display: flex;
span {
flex: 1;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable">
<template #toolbar>
<a-tooltip placement="top">
<template #title>
<span>执行队列</span>
</template>
<i class="icon-ym icon-ym-generator-slider cursor-pointer text-18px" @click="openDrawer(true)"></i>
</a-tooltip>
</template>
<template #headerTop>
<a-alert message="请使用【任务流程】功能来搭建业务编排,集成助手版块停止更新,后续将下架!" showIcon type="warning" class="mt-10px" />
</template>
<template #tableTitle>
<a-dropdown>
<template #overlay>
<a-menu @click="handleAdd">
<a-menu-item :key="item.id" v-for="item in typeList">{{ item.fullName }}</a-menu-item>
</a-menu>
</template>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add">{{ t('common.addText') }}<DownOutlined /></a-button>
</a-dropdown>
<yunzhupaas-upload-btn url="/api/visualdev/Integrate/Actions/Import" accept=".bi" @on-success="reload"></yunzhupaas-upload-btn>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
{{ record.type === 1 ? '事件触发' : record.type === 2 ? '定时触发' : 'webhook触发' }}
</template>
<template v-if="column.key === 'enabledMark'">
<a-tag :color="record.enabledMark === 1 ? 'success' : 'error'">{{ record.enabledMark == 1 ? '启用' : '禁用' }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" @design="handleDesign" />
<IntegrateProcess @register="registerIntegrateProcess" @reload="reload" />
<ExecutionQueue @register="registerDrawer" />
<Log @register="registerLogPopup" @updateNodes="updateNodes" />
</div>
</template>
<script lang="ts" setup>
import { getIntegrateList, delIntegrate, copy, exportData, updateState } from '@/api/onlineDev/integrate';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useModal } from '@/components/Modal';
import { usePopup } from '@/components/Popup/src/usePopup';
import { IntegrateProcess } from '@/components/IntegrateProcess';
import { downloadByUrl } from '@/utils/file/download';
import { DownOutlined } from '@ant-design/icons-vue';
import { useDrawer } from '@/components/Drawer';
import Form from './Form.vue';
import ExecutionQueue from './components/ExecutionQueue.vue';
import Log from './components/Log.vue';
defineOptions({ name: 'onlineDev-integrate' });
const { createMessage } = useMessage();
const { t } = useI18n();
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerIntegrateProcess, { openModal: openIntegrateProcess }] = useModal();
const [registerDrawer, { openDrawer }] = useDrawer();
const [registerLogPopup, { openPopup: openLogPopup }] = usePopup();
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName', width: 200 },
{ title: '编码', dataIndex: 'enCode', width: 180 },
{ title: '类型', dataIndex: 'type', width: 110, align: 'center' },
{ title: '创建人', dataIndex: 'creatorUser', width: 120 },
{ title: '创建时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '最后修改时间', dataIndex: 'lastModifyTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '状态', dataIndex: 'enabledMark', width: 80, align: 'center' },
];
const typeList = [
{ fullName: '事件触发', id: 1 },
{ fullName: '定时触发', id: 2 },
{ fullName: 'webhook触发', id: 3 },
];
const [registerTable, { reload }] = useTable({
api: getIntegrateList,
columns,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'type',
label: '类型',
component: 'Select',
componentProps: { placeholder: '请选择', options: typeList },
},
{
field: 'enabledMark',
label: '状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '启用', id: 1 },
{ fullName: '禁用', id: 0 },
],
},
},
],
},
actionColumn: {
width: 180,
title: '操作',
dataIndex: 'action',
},
});
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText'),
onClick: addOrUpdateHandle.bind(null, record.id),
},
{
label: record.enabledMark == 1 ? '禁用' : '启用',
color: 'error',
modelConfirm: {
content: `此操作将${record.enabledMark == 1 ? '禁用' : '启用'}该集成助手,是否继续?`,
onOk: handleRelease.bind(null, record.id),
},
},
{
label: '设计',
onClick: handleDesign.bind(null, record.id),
},
];
}
function getDropDownActions(record): ActionItem[] {
return [
{
label: '日志',
onClick: handleLog.bind(null, record.id, record.fullName),
},
{
label: t('common.copyText'),
modelConfirm: {
content: '您确定要复制该集成助手, 是否继续?',
onOk: handleCopy.bind(null, record.id),
},
},
{
label: t('common.exportText'),
modelConfirm: {
content: '您确定要导出该集成助手, 是否继续?',
onOk: handleExport.bind(null, record.id),
},
},
{
label: t('common.delText'),
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function handleAdd({ key }) {
addOrUpdateHandle('', key);
}
function addOrUpdateHandle(id = '', type) {
openFormModal(true, { id, type });
}
function handleDelete(id) {
delIntegrate(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDesign(id) {
openIntegrateProcess(true, { id });
}
function handleCopy(id) {
copy(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleExport(id) {
exportData(id).then(res => {
downloadByUrl({ url: res.data.url });
});
}
function handleLog(id, fullName) {
openLogPopup(true, { id, fullName });
}
function handleRelease(id) {
updateState(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function updateNodes(id) {
handleDesign(id);
}
</script>

View File

@@ -0,0 +1,157 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="dataForm.id ? t('common.editText') : t('common.addText')" @ok="handleSubmit()">
<BasicForm @register="registerForm">
<template #icon="{ model, field }">
<div class="flex">
<div class="flex-1 mr-10px">
<yunzhupaas-icon-picker v-model:value="model[field]" placeholder="请选择" />
</div>
<a-form-item-rest>
<yunzhupaas-color-picker v-model:value="state.iconBackground" size="small" :predefine="predefineList" name="iconBackground" />
</a-form-item-rest>
</div>
</template>
</BasicForm>
<template #appendFooter>
<a-button type="primary" :loading="btnLoading" @click="handleSubmit(1)">确定并设计</a-button>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { toRefs, reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import formValidate from '@/utils/formValidate';
import { getPrintDevInfo, updatePrintDev, createPrintDev } from '@/api/system/printDev';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
dataForm: any;
btnLoading: boolean;
iconBackground: string;
}
const predefineList = ['#008cff', '#35b8e0', '#00cc88', '#ff9d00', '#ff4d4d', '#5b69bc', '#ff8acc', '#3b3e47', '#282828'];
const state = reactive<State>({
dataForm: {},
btnLoading: false,
iconBackground: '',
});
const { dataForm, btnLoading } = toRefs(state);
const { createMessage } = useMessage();
const { t } = useI18n();
const emit = defineEmits(['register', 'reload', 'design']);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
const [registerForm, { setFieldsValue, resetFields, validate, updateSchema }] = useForm({
schemas: [
{
field: 'fullName',
label: '名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 100 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: { placeholder: '请选择', showSearch: true },
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
{
field: 'sortCode',
label: '排序',
defaultValue: 0,
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
},
{
field: 'commonUse',
label: '通用',
helpMessage: '启用后,将该打印设计设为通用打印模板',
defaultValue: 0,
component: 'Switch',
},
{
ifShow: ({ values }) => values.commonUse === 1,
field: 'visibleType',
label: '发布范围',
defaultValue: 1,
component: 'Radio',
componentProps: {
options: [
{ fullName: '公开', id: 1 },
{ fullName: '权限设置', id: 2 },
],
},
},
{
ifShow: ({ values }) => values.commonUse === 1,
field: 'icon',
label: '图标',
slot: 'icon',
component: 'IconPicker',
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
{
field: 'description',
label: '说明 ',
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
],
});
function init(data) {
resetFields();
state.iconBackground = '#008cff';
state.dataForm.id = data.id || '';
updateSchema([{ field: 'category', componentProps: { options: data.categoryList || [] } }]);
if (state.dataForm.id) {
changeLoading(true);
getPrintDevInfo(state.dataForm.id)
.then(res => {
state.dataForm = res.data;
state.iconBackground = state.dataForm.iconBackground || '#008cff';
state.dataForm.visibleType = state.dataForm.visibleType || 1;
setFieldsValue(state.dataForm);
changeLoading(false);
})
.catch(() => {
changeLoading(false);
});
}
}
async function handleSubmit(type = 0) {
const values = await validate();
if (!values) return;
type === 1 ? (state.btnLoading = true) : changeOkLoading(true);
const query = { ...values, iconBackground: state.iconBackground, id: state.dataForm.id };
const formMethod = state.dataForm.id ? updatePrintDev : createPrintDev;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
state.btnLoading = false;
closeModal();
emit('reload');
if (!state.dataForm.id) state.dataForm.id = res.data;
if (type == 1) emit('design', { ...values, id: state.dataForm.id });
})
.catch(() => {
changeOkLoading(false);
state.btnLoading = false;
});
}
</script>

View File

@@ -0,0 +1,181 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ t('common.addText') }}</a-button>
<yunzhupaas-upload-btn url="/api/system/printDev/Actions/Import" accept=".bp" @on-success="reload"></yunzhupaas-upload-btn>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<a-tag :color="record.state == 1 ? 'success' : ''">{{ record.state == 1 ? '已发布' : '未发布' }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" @design="handleDesign" />
<PrintDesign @register="registerPrintDesign" @reload="reload" />
<Preview @register="registerPreview" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { getPrintDevList, delPrintDev, copy, exportData } from '@/api/system/printDev';
import { useModal } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import { downloadByUrl } from '@/utils/file/download';
import { useBaseStore } from '@/store/modules/base';
import PrintDesign from '@/components/PrintDesign/index.vue';
import Preview from '@/components/PrintDesign/Preview.vue';
import Form from './Form.vue';
defineOptions({ name: 'onlineDev-printDev' });
const { t } = useI18n();
const baseStore = useBaseStore();
const { createMessage } = useMessage();
const categoryList = ref<any[]>([]);
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName' },
{ title: '编码', dataIndex: 'enCode', width: 200 },
{ title: '分类', dataIndex: 'category', width: 150 },
{ title: '创建人', dataIndex: 'creatorUser', width: 120 },
{ title: '创建时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '最后修改时间', dataIndex: 'lastModifyTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '排序', dataIndex: 'sortCode', width: 70, align: 'center' },
{ title: '状态', dataIndex: 'state', width: 80, align: 'center' },
];
const [registerTable, { reload, getForm }] = useTable({
api: getPrintDevList,
columns,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: {
placeholder: '请选择',
},
},
{
field: 'state',
label: '状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '未发布', id: 0 },
{ fullName: '已发布', id: 1 },
],
},
},
],
},
actionColumn: {
width: 150,
title: '操作',
dataIndex: 'action',
},
});
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerPrintDesign, { openModal: openPrintDesign }] = useModal();
const [registerPreview, { openModal: openPreviewModal }] = useModal();
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText'),
onClick: addOrUpdateHandle.bind(null, record.id),
},
{
label: '设计',
color: 'error',
onClick: handleDesign.bind(null, record),
},
];
}
function getDropDownActions(record): ActionItem[] {
return [
{
label: t('common.previewText'),
ifShow: !!record.state,
onClick: handlePreview.bind(null, record.id, record.fullName),
},
{
label: t('common.copyText'),
ifShow: !!record.state,
modelConfirm: {
content: '您确定要复制该打印模板, 是否继续?',
onOk: handleCopy.bind(null, record.id),
},
},
{
label: t('common.exportText'),
ifShow: !!record.state,
modelConfirm: {
content: '您确定要导出该打印模板, 是否继续?',
onOk: handleExport.bind(null, record.id),
},
},
{
label: t('common.delText'),
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function addOrUpdateHandle(id = '') {
openFormModal(true, { id, categoryList: categoryList.value });
}
function handleExport(id) {
exportData(id).then(res => {
downloadByUrl({ url: res.data.url });
});
}
function handleCopy(id) {
copy(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDelete(id) {
delPrintDev(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDesign(record) {
openPrintDesign(true, record);
}
function handlePreview(id, fullName) {
openPreviewModal(true, { id, fullName });
}
async function getOptions() {
categoryList.value = (await baseStore.getDictionaryData('businessType')) as any[];
getForm().updateSchema({ field: 'category', componentProps: { options: categoryList.value } });
}
onMounted(() => {
getOptions();
});
</script>

View File

@@ -0,0 +1,127 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="生成菜单" @ok="handleSubmit" class="yunzhupaas-release-modal">
<a-alert message="将该报表发布至应用菜单" type="warning" show-icon />
<a-form class="release-main" :colon="false" :model="dataForm" :rules="rules" layout="vertical" ref="formElRef">
<div class="release-item report-item-left">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.pc === 1 }" @click="selectToggle('pc')">
<i class="item-icon icon-ym icon-ym-pc"></i>
<p class="item-title">桌面端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="上级" name="pcModuleParentId" v-if="dataForm.pc">
<YunzhupaasTreeSelect
v-model:value="pcModuleParentId"
:options="treeData"
treeCheckStrictly
multiple
:dropdownMatchSelectWidth="false"
@change="onPcChange" />
</a-form-item>
<a-form-item label="已发布菜单路径" v-if="record.pcIsRelease">
<div class="released">{{ record.pcReleaseName }}</div>
</a-form-item>
</div>
</a-form>
</BasicModal>
</template>
<script lang="ts" setup>
import { getReleaseMenu, createMenu } from '@/api/onlineDev/report';
import { BasicModal, useModalInner } from '@/components/Modal';
import { ref, reactive, toRefs, computed } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import { getMenuSelectorFilter } from '@/api/system/menu';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
dataForm: any;
record: any;
treeData: any[];
pcModuleParentId: any[];
}
const emit = defineEmits(['register', 'reload']);
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const [registerModal, { changeOkLoading, closeModal }] = useModalInner(init);
const formElRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
pc: 1,
pcModuleParentId: [],
},
record: {},
treeData: [],
pcModuleParentId: [],
});
const { dataForm, record, treeData, pcModuleParentId } = toRefs(state);
const rules = computed(() => {
let rules: any = {
pcModuleParentId: [],
};
if (!state.record.pcIsRelease) rules.pcModuleParentId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
return rules;
});
function init(data) {
state.pcModuleParentId = [];
getReleaseMenu(data.id).then(res => {
state.record = res.data;
const platformRelease = res.data.platformRelease ? JSON.parse(res.data.platformRelease) : {};
state.dataForm = {
pc: platformRelease.pc === 0 ? 0 : 1,
pcModuleParentId: [],
};
formElRef.value?.clearValidate();
});
getMenuOptions(data.id);
}
function getMenuOptions(id) {
getMenuSelectorFilter({ category: 'Web' }, id).then(res => {
state.treeData = res.data.list;
});
}
function onPcChange(data) {
state.dataForm.pcModuleParentId = data.map(o => o.value);
}
function selectToggle(key) {
state.dataForm[key] = state.dataForm[key] === 1 ? 0 : 1;
}
async function handleSubmit() {
try {
if (!state.dataForm.pc) return createMessage.error('请选择发布类型');
const values = await formElRef.value?.validate();
if (!values) return;
const platform = { pc: state.dataForm.pc };
const query = { ...state.dataForm, platformRelease: JSON.stringify(platform) };
const handleRelease = () => {
changeOkLoading(true);
createMenu(state.record.id, query)
.then(res => {
changeOkLoading(false);
createMessage.success(res.msg);
emit('reload');
closeModal();
})
.catch(() => {
changeOkLoading(false);
});
};
if (!state.record.isRelease) return handleRelease();
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '发布确定后会覆盖当前线上版本且进行菜单同步,是否继续?',
onOk: handleRelease,
});
} catch (_) {}
}
</script>

View File

@@ -0,0 +1,112 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="dataForm.id ? t('common.editText') : t('common.addText')" @ok="handleSubmit()">
<BasicForm @register="registerForm" />
<template #appendFooter>
<a-button type="primary" :loading="btnLoading" @click="handleSubmit(1)">确定并设计</a-button>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { toRefs, reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import formValidate from '@/utils/formValidate';
import { getReportInfo, updateReport, createReport } from '@/api/onlineDev/report';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
dataForm: any;
btnLoading: boolean;
}
const state = reactive<State>({
dataForm: {},
btnLoading: false,
});
const { dataForm, btnLoading } = toRefs(state);
const { createMessage } = useMessage();
const { t } = useI18n();
const emit = defineEmits(['register', 'reload', 'design']);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
const [registerForm, { setFieldsValue, resetFields, validate, updateSchema }] = useForm({
schemas: [
{
field: 'fullName',
label: '名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 100 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: { placeholder: '请选择', showSearch: true },
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
{
field: 'sortCode',
label: '排序',
defaultValue: 0,
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
},
{
field: 'description',
label: '说明 ',
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
],
});
function init(data) {
resetFields();
state.dataForm.id = data.id || '';
updateSchema([{ field: 'category', componentProps: { options: data.categoryList || [] } }]);
if (state.dataForm.id) {
changeLoading(true);
getReportInfo(state.dataForm.id)
.then(res => {
state.dataForm = res.data;
setFieldsValue(state.dataForm);
changeLoading(false);
})
.catch(() => {
changeLoading(false);
});
}
}
async function handleSubmit(type = 0) {
const values = await validate();
if (!values) return;
type === 1 ? (state.btnLoading = true) : changeOkLoading(true);
const query = { ...values, id: state.dataForm.id };
const formMethod = state.dataForm.id ? updateReport : createReport;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
state.btnLoading = false;
closeModal();
emit('reload');
if (!state.dataForm.id) state.dataForm.id = res.data;
if (type == 1) emit('design', { ...values, id: state.dataForm.id });
})
.catch(() => {
changeOkLoading(false);
state.btnLoading = false;
});
}
</script>

View File

@@ -0,0 +1,191 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ t('common.addText') }}</a-button>
<yunzhupaas-upload-btn url="/api/Report/Actions/Import" accept=".rp" @on-success="reload" type="report" />
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<a-tag :color="record.enabledMark == 1 ? 'success' : ''">{{ record.enabledMark == 1 ? '已发布' : '未发布' }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" @design="handleDesign" />
<Report @register="registerDesign" @reload="reload" />
<ReportPreview @register="registerPreview" />
<CreateMenuModal @register="registerCreateMenu" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { getReportList, delReport, copy, exportData } from '@/api/onlineDev/report';
import { useModal } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import { downloadByUrlReport } from '@/utils/file/download';
import { useBaseStore } from '@/store/modules/base';
import { Report, ReportPreview } from '@/components/Report';
import Form from './Form.vue';
import CreateMenuModal from './CreateMenuModal.vue';
defineOptions({ name: 'onlineDev-report' });
const { t } = useI18n();
const baseStore = useBaseStore();
const { createMessage } = useMessage();
const categoryList = ref<any[]>([]);
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName' },
{ title: '编码', dataIndex: 'enCode', width: 200 },
{ title: '分类', dataIndex: 'category', width: 150 },
{ title: '创建人', dataIndex: 'creatorUser', width: 120 },
{ title: '创建时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '最后修改时间', dataIndex: 'lastModifyTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '排序', dataIndex: 'sortCode', width: 70, align: 'center' },
{ title: '状态', dataIndex: 'state', width: 80, align: 'center' },
];
const [registerTable, { reload, getForm }] = useTable({
api: getReportList,
columns,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: {
placeholder: '请选择',
},
},
{
field: 'state',
label: '状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '未发布', id: 0 },
{ fullName: '已发布', id: 1 },
],
},
},
],
},
actionColumn: {
width: 220,
title: '操作',
dataIndex: 'action',
},
});
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerDesign, { openModal: openDesign }] = useModal();
const [registerPreview, { openModal: openPreviewModal }] = useModal();
const [registerCreateMenu, { openModal: openCreateMenuModal }] = useModal();
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText'),
onClick: addOrUpdateHandle.bind(null, record.id),
},
{
label: '设计',
color: 'error',
onClick: handleDesign.bind(null, record),
},
{
label: '生成菜单',
disabled: !record.state,
onClick: handleCreateMenu.bind(null, record),
},
];
}
function getDropDownActions(record): ActionItem[] {
return [
{
label: t('common.previewText'),
ifShow: !!record.state,
onClick: handlePreview.bind(null, record.id),
},
{
label: t('common.copyText'),
ifShow: !!record.state,
modelConfirm: {
content: '您确定要复制该模板, 是否继续?',
onOk: handleCopy.bind(null, record.id),
},
},
{
label: t('common.exportText'),
ifShow: !!record.state,
modelConfirm: {
content: '您确定要导出该模板, 是否继续?',
onOk: handleExport.bind(null, record.id),
},
},
{
label: t('common.delText'),
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function addOrUpdateHandle(id = '') {
openFormModal(true, { id, categoryList: categoryList.value });
}
function handleExport(id) {
exportData(id).then(res => {
downloadByUrlReport({ url: res.data.url });
});
}
function handleCopy(id) {
copy(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDelete(id) {
delReport(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDesign(record) {
openDesign(true, record);
}
function handlePreview(id) {
openPreviewModal(true, { id });
}
async function getOptions() {
categoryList.value = (await baseStore.getDictionaryData('businessType')) as any[];
getForm().updateSchema({ field: 'category', componentProps: { options: categoryList.value } });
}
function handleCreateMenu(data) {
openCreateMenuModal(true, data);
}
onMounted(() => {
getOptions();
});
</script>

View File

@@ -0,0 +1,191 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="state.dataForm.id ? t('common.editText') : t('common.addText')" @ok="handleSubmit">
<a-alert message="新建成功后,前往【系统管理】>【应用菜单】中添加应用门户并授权。" type="warning" show-icon v-if="!dataForm.id" class="!mb-20px" />
<BasicForm @register="registerForm" />
<template #appendFooter>
<a-button type="primary" :loading="btnLoading" @click="handleSubmit(1)" v-if="dataForm.type === 0">确定并设计</a-button>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { toRefs, reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import formValidate from '@/utils/formValidate';
import { getInfo, updatePortal, createPortal } from '@/api/onlineDev/portal';
import { useMessage } from '@/hooks/web/useMessage';
import { isUrl } from '@/utils/is';
import { useI18n } from '@/hooks/web/useI18n';
const { t } = useI18n();
const validateUrl = (_rule, value) => {
if (value && getFieldsValue()?.linkType == 1 && !isUrl(value)) return Promise.reject('请输入正确的链接地址');
return Promise.resolve();
};
interface State {
dataForm: any;
btnLoading: boolean;
}
const state = reactive<State>({
dataForm: {
type: 0,
},
btnLoading: false,
});
const { dataForm, btnLoading } = toRefs(state);
const { createMessage } = useMessage();
const emit = defineEmits(['register', 'reload', 'design']);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
const [registerForm, { setFieldsValue, getFieldsValue, resetFields, validate, clearValidate, updateSchema }] = useForm({
labelWidth: 100,
schemas: [
{
field: 'fullName',
label: '名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 100 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: { placeholder: '请选择', showSearch: true },
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
{
field: 'type',
label: '类型',
defaultValue: 0,
component: 'Radio',
componentProps: {
options: [
{ id: 0, fullName: '门户设计' },
{ id: 1, fullName: '配置路径' },
],
optionType: 'button',
buttonStyle: 'solid',
onChange: onTypeChange,
},
rules: [{ required: true, trigger: 'change', message: '必填', type: 'number' }],
},
{
ifShow: ({ values }) => values.type === 1,
field: 'linkType',
label: '链接类型',
defaultValue: 0,
component: 'Radio',
componentProps: {
options: [
{ id: 0, fullName: '页面' },
{ id: 1, fullName: '外链' },
],
optionType: 'button',
buttonStyle: 'solid',
onChange: onLinkTypeChange,
},
rules: [{ required: true, trigger: 'change', message: '必填', type: 'number' }],
},
{
ifShow: ({ values }) => values.type === 1,
field: 'customUrl',
label: '链接地址',
component: 'Input',
componentProps: { placeholder: '请输入' },
helpMessage: '链接类型选择页面只支持PC显示不支持APP显示。',
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: validateUrl, trigger: 'blur' },
],
},
{
ifShow: ({ values }) => values.type === 0,
field: 'enabledLock',
label: '锁定',
component: 'Switch',
defaultValue: 1,
helpMessage: '启用不允许拖拽移动控件禁用允许用户在PC门户上拖拽大小及移动控件。',
},
{
field: 'sortCode',
label: '排序',
defaultValue: 0,
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
},
{
field: 'description',
label: '说明 ',
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
],
});
function init(data) {
resetFields();
state.dataForm.id = data.id || '';
updateSchema([{ field: 'category', componentProps: { options: data.categoryList || [] } }]);
state.dataForm.type = 0;
if (state.dataForm.id) {
changeLoading(true);
getInfo(state.dataForm.id)
.then(res => {
state.dataForm = res.data;
onLinkTypeChange(state.dataForm.linkType);
setFieldsValue(state.dataForm);
changeLoading(false);
})
.catch(() => {
changeLoading(false);
});
}
}
function onLinkTypeChange(val) {
updateSchema([
{ field: 'customUrl', componentProps: { addonBefore: val === 0 ? '@/views/' : '' }, helpMessage: val === 1 ? '地址以http://或https://为开头' : '' },
]);
setFieldsValue({ customUrl: '' });
clearValidate('customUrl');
}
function onTypeChange(val) {
state.dataForm.type = val;
onLinkTypeChange(getFieldsValue()?.linkType);
}
async function handleSubmit(type?) {
const values = await validate();
if (!values) return;
type === 1 ? (state.btnLoading = true) : changeOkLoading(true);
const query = {
...values,
id: state.dataForm.id,
};
const formMethod = state.dataForm.id ? updatePortal : createPortal;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
state.btnLoading = false;
closeModal();
emit('reload');
if (!state.dataForm.id) state.dataForm.id = res.data;
if (type == 1) emit('design', { ...values, id: state.dataForm.id });
})
.catch(() => {
changeOkLoading(false);
state.btnLoading = false;
});
}
</script>

View File

@@ -0,0 +1,269 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="发布" @ok="handleSubmit" class="yunzhupaas-release-modal">
<a-form :colon="false" :model="dataForm" :rules="rules" layout="vertical" ref="formElRef">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="first" tab="主页门户">
<a-alert message="将该门户发布至应用主页门户" type="warning" show-icon />
<div class="release-main">
<div class="release-item">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.pcPortal === 1 }" @click="selectToggle('pcPortal')">
<i class="item-icon icon-ym icon-ym-pc"></i>
<p class="item-title">桌面端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="应用" name="pcPortalSystemId" v-if="dataForm.pcPortal">
<YunzhupaasSelect v-model:value="dataForm.pcPortalSystemId" :options="treeData" multiple allowClear />
</a-form-item>
<a-form-item label="已发布应用" v-if="record.pcPortalIsRelease">
<div class="released">{{ record.pcPortalReleaseName }}</div>
</a-form-item>
</div>
<div class="release-item">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.appPortal === 1 }" @click="selectToggle('appPortal')">
<i class="item-icon icon-ym icon-ym-mobile"></i>
<p class="item-title">移动端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="应用" name="appPortalSystemId" v-if="dataForm.appPortal">
<YunzhupaasSelect v-model:value="dataForm.appPortalSystemId" :options="treeAppData" multiple allowClear />
</a-form-item>
<a-form-item label="已发布应用" v-if="record.appPortalIsRelease">
<div class="released">{{ record.appPortalReleaseName }}</div>
</a-form-item>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="second" tab="应用菜单" force-render>
<a-alert message="将该门户发布至应用菜单" type="warning" show-icon />
<div class="release-main">
<div class="release-item">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.pc === 1 }" @click="selectToggle('pc')">
<i class="item-icon icon-ym icon-ym-pc"></i>
<p class="item-title">桌面端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="上级" name="pcModuleParentId" v-if="dataForm.pc">
<YunzhupaasTreeSelect
v-model:value="pcModuleParentId"
:options="menuTreeData"
treeCheckStrictly
multiple
:dropdownMatchSelectWidth="false"
@change="onPcChange" />
</a-form-item>
<a-form-item label="已发布菜单路径" v-if="record.pcIsRelease">
<div class="released">{{ record.pcReleaseName }}</div>
</a-form-item>
</div>
<div class="release-item">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.app === 1 }" @click="selectToggle('app')">
<i class="item-icon icon-ym icon-ym-mobile"></i>
<p class="item-title">移动端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="上级" name="appModuleParentId" v-if="dataForm.app">
<YunzhupaasTreeSelect
v-model:value="appModuleParentId"
:options="appMenuTreeData"
treeCheckStrictly
multiple
:dropdownMatchSelectWidth="false"
@change="onAppChange" />
</a-form-item>
<a-form-item label="已发布菜单路径" v-if="record.appIsRelease">
<div class="released">{{ record.appReleaseName }}</div>
</a-form-item>
</div>
</div>
</a-tab-pane>
</a-tabs>
</a-form>
</BasicModal>
</template>
<script lang="ts" setup>
import { release, getSystemListFilter, getInfo } from '@/api/onlineDev/portal';
import { BasicModal, useModalInner } from '@/components/Modal';
import { ref, reactive, toRefs, computed } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import { getMenuSelectorFilter } from '@/api/system/menu';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
dataForm: any;
record: any;
treeData: any[];
treeAppData: any[];
menuTreeData: any[];
appMenuTreeData: any[];
pcModuleParentId: any[];
appModuleParentId: any[];
activeKey: String;
}
const emit = defineEmits(['register', 'reload']);
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const [registerModal, { changeOkLoading, closeModal }] = useModalInner(init);
const formElRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
pc: 1,
app: 1,
pcModuleParentId: [],
appModuleParentId: [],
pcPortal: 1,
appPortal: 1,
pcPortalSystemId: [],
appPortalSystemId: [],
},
record: {},
treeData: [],
treeAppData: [],
menuTreeData: [],
appMenuTreeData: [],
pcModuleParentId: [],
appModuleParentId: [],
activeKey: 'first',
});
const { dataForm, record, treeData, treeAppData, menuTreeData, appMenuTreeData, activeKey, pcModuleParentId, appModuleParentId } = toRefs(state);
const rules = computed(() => {
let rules: any = {
pcModuleParentId: [],
appModuleParentId: [],
pcPortalSystemId: [],
appPortalSystemId: [],
};
if (!state.record.pcPortalIsRelease) rules.pcPortalSystemId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
if (!state.record.appPortalIsRelease) rules.appPortalSystemId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
if (!state.record.pcIsRelease) rules.pcModuleParentId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
if (!state.record.appIsRelease) rules.appModuleParentId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
return rules;
});
function init(data) {
state.activeKey = 'first';
state.pcModuleParentId = [];
state.appModuleParentId = [];
getInfo(data.id).then(res => {
state.record = res.data;
const platformRelease = res.data.platformRelease ? JSON.parse(res.data.platformRelease) : {};
state.dataForm = {
pc: platformRelease.pc === 0 ? 0 : 1,
app: platformRelease.app === 0 ? 0 : 1,
pcModuleParentId: [],
appModuleParentId: [],
pcPortal: platformRelease.pcPortal === 0 ? 0 : 1,
appPortal: platformRelease.appPortal === 0 ? 0 : 1,
pcPortalSystemId: [],
appPortalSystemId: [],
};
formElRef.value?.clearValidate();
});
getSystemOptions(data.id);
getAppSystemOptions(data.id);
getMenuOptions(data.id);
getAppMenuOptions(data.id);
}
function getSystemOptions(id) {
getSystemListFilter({ category: 'Web' }, id).then(res => {
state.treeData = res.data.list || [];
});
}
function getAppSystemOptions(id) {
getSystemListFilter({ category: 'App' }, id).then(res => {
state.treeAppData = res.data.list || [];
});
}
function selectToggle(key) {
state.dataForm[key] = state.dataForm[key] === 1 ? 0 : 1;
}
async function handleSubmit() {
try {
if (!state.dataForm.pc && !state.dataForm.app && !state.dataForm.pcPortal && !state.dataForm.appPortal)
return createMessage.error('请至少选择一种发布类型');
const values = await formElRef.value?.validate();
if (!values) return;
const platform = {
pc: state.dataForm.pc,
app: state.dataForm.app,
pcPortal: state.dataForm.pcPortal,
appPortal: state.dataForm.appPortal,
};
const query = { ...state.dataForm, platformRelease: JSON.stringify(platform) };
const handleRelease = () => {
changeOkLoading(true);
release(state.record.id, query)
.then(res => {
changeOkLoading(false);
createMessage.success(res.msg);
emit('reload');
closeModal();
})
.catch(() => {
changeOkLoading(false);
});
};
if (!state.record.isRelease) return handleRelease();
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '发布确定后会覆盖当前线上版本且进行门户同步,是否继续?',
onOk: handleRelease,
});
} catch (_) {
if (
(state.dataForm.pcPortalSystemId.length || !state.dataForm.pcPortal) &&
(state.dataForm.appPortalSystemId.length || !state.dataForm.appPortal) &&
!state.dataForm.pcModuleParentId.length &&
!state.dataForm.appModuleParentId.length
)
return createMessage.error('应用菜单的上级必填');
if (
!state.dataForm.pcPortalSystemId.length &&
!state.dataForm.appPortalSystemId.length &&
(state.dataForm.pcModuleParentId.length || !state.dataForm.pc) &&
(state.dataForm.appModuleParentId.length || !state.dataForm.app)
)
return createMessage.error('主页门户的应用必填');
}
}
function getMenuOptions(id) {
getMenuSelectorFilter({ category: 'Web' }, id).then(res => {
state.menuTreeData = res.data.list || [];
});
}
function getAppMenuOptions(id) {
getMenuSelectorFilter({ category: 'App' }, id).then(res => {
state.appMenuTreeData = res.data.list || [];
for (let index = 0; index < state.appMenuTreeData.length; index++) {
const item = state.appMenuTreeData[index];
if (item.type == 0) item.disabled = true;
}
});
}
function onPcChange(data) {
state.dataForm.pcModuleParentId = data.map(o => o.value);
}
function onAppChange(data) {
state.dataForm.appModuleParentId = data.map(o => o.value);
}
</script>

View File

@@ -0,0 +1,238 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ t('common.addText') }}</a-button>
<yunzhupaas-upload-btn url="/api/visualdev/Portal/Actions/Import" accept=".vp" @on-success="reload"></yunzhupaas-upload-btn>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'isRelease'">
<a-tag :color="record.isRelease === 1 ? 'success' : record.isRelease == 2 ? 'warning' : ''">
{{ record.isRelease == 1 ? '已发布' : record.isRelease == 2 ? '已修改' : '未发布' }}
</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" @design="handleDesignFun" />
<PortalDesign @register="registerPortalDesign" @reload="reload" />
<ReleaseModal @register="registerReleaseModal" @reload="reload" />
<PreviewModal @register="registerPreviewModal" @previewPc="previewPc" />
<Preview @register="registerPreview" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getPortalList, delPortal, copyPortal, exportPortal } from '@/api/onlineDev/portal';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useModal } from '@/components/Modal';
import { useBaseStore } from '@/store/modules/base';
import Form from './Form.vue';
import PortalDesign from '@/components/VisualPortal/Design/index.vue';
import ReleaseModal from './components/ReleaseModal.vue';
import { downloadByUrl } from '@/utils/file/download';
import Preview from '@/components/VisualPortal/Design/components/Preview.vue';
import { PreviewModal } from '@/components/CommonModal';
defineOptions({ name: 'onlineDev-visualPortal' });
const { createMessage } = useMessage();
const baseStore = useBaseStore();
const { t } = useI18n();
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerPortalDesign, { openModal: openPortalDesign }] = useModal();
const [registerReleaseModal, { openModal: openReleaseModal }] = useModal();
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const [registerPreview, { openModal: openPreview }] = useModal();
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName', width: 200 },
{ title: '编码', dataIndex: 'enCode', width: 180 },
{ title: '分类', dataIndex: 'category', width: 100 },
{ title: '类型', dataIndex: 'type', width: 100, align: 'center', customRender: ({ record }) => (record.type === 1 ? '配置路径' : '门户设计') },
{
title: '锁定',
dataIndex: 'enabledLock',
width: 100,
align: 'center',
customRender: ({ record }) => (record.type === 1 ? '' : record.enabledLock === 1 ? '是' : '否'),
},
{ title: '创建人', dataIndex: 'creatorUser', width: 120 },
{ title: '创建时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '最后修改时间', dataIndex: 'lastModifyTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '排序', dataIndex: 'sortCode', width: 70, align: 'center' },
{ title: '发布状态', dataIndex: 'isRelease', width: 80, align: 'center' },
];
const typeList = [
{ fullName: '配置路径', id: 1 },
{ fullName: '门户设计', id: 0 },
];
const enabledLockList = [
{ fullName: '是', id: 1 },
{ fullName: '否', id: 0 },
];
const categoryList = ref<any[]>([]);
const [registerTable, { reload, getForm }] = useTable({
api: getPortalList,
columns,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: {
placeholder: '请选择',
showSearch: true,
},
},
{
field: 'type',
label: '类型',
component: 'Select',
componentProps: {
placeholder: '请选择',
showSearch: true,
options: typeList,
},
},
{
field: 'enabledLock',
label: '锁定',
component: 'Select',
componentProps: {
placeholder: '请选择',
showSearch: true,
options: enabledLockList,
},
},
{
field: 'isRelease',
label: '发布状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '未发布', id: 0 },
{ fullName: '已发布', id: 1 },
{ fullName: '已修改', id: 2 },
],
},
},
],
},
actionColumn: {
width: 180,
title: '操作',
dataIndex: 'action',
},
});
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText'),
onClick: addOrUpdateHandle.bind(null, record.id),
},
{
label: '发布',
color: 'error',
onClick: handleRelease.bind(null, record.id),
},
{
label: '设计',
disabled: record.type !== 0,
onClick: handleDesign.bind(null, record),
},
];
}
function getDropDownActions(record): ActionItem[] {
return [
{
label: t('common.previewText'),
onClick: handlePreview.bind(null, record.id),
},
{
label: t('common.copyText'),
modelConfirm: {
content: '您确定要复制该门户, 是否继续?',
onOk: handleCopy.bind(null, record.id),
},
},
{
label: t('common.exportText'),
modelConfirm: {
content: '您确定要导出该门户, 是否继续?',
onOk: handleExport.bind(null, record.id),
},
},
{
label: t('common.delText'),
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function addOrUpdateHandle(id = '') {
openFormModal(true, { id, categoryList });
}
function handleDelete(id) {
delPortal(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleRelease(id) {
openReleaseModal(true, { id });
}
function handleDesign(record) {
openPortalDesign(true, record);
}
function handleDesignFun(item) {
handleDesign(item);
}
function handlePreview(id) {
openPreviewModal(true, { type: 'portal', id });
}
function previewPc({ id }) {
openPreview(true, { id });
}
function handleCopy(id) {
copyPortal(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleExport(id) {
exportPortal(id).then(res => {
downloadByUrl({ url: res.data.url });
});
}
async function getOptions() {
const res = await baseStore.getDictionaryData('businessType');
categoryList.value = res as any[];
getForm().updateSchema({ field: 'category', componentProps: { options: res } });
}
onMounted(() => {
getOptions();
});
</script>

View File

@@ -0,0 +1,578 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
defaultFullscreen
:footer="null"
:closable="false"
:keyboard="false"
class="yunzhupaas-full-modal full-modal designer-modal"
destroy-on-close>
<template #title>
<div class="yunzhupaas-full-modal-header">
<div class="header-title">
<img src="@/assets/images/yunzhupaas.png" class="header-logo" />
<span class="header-dot"></span>
<p class="header-txt" v-if="!activeStep">在线开发</p>
<a-tooltip :title="dataForm.fullName" v-else>
<p class="header-txt">{{ dataForm.fullName }}</p>
</a-tooltip>
</div>
<a-steps v-model:current="activeStep" type="navigation" size="small" @change="onStepChange" class="header-steps">
<a-step title="基础设计" />
<a-step title="表单设计" :disabled="activeStep <= 1" />
<a-step title="列表设计" disabled v-if="maxStep >= 2" />
</a-steps>
<a-space class="options" :size="10">
<a-button shape="round" type="warning" @click="toggleWebType(1)" v-show="activeStep == 2 && dataForm.webType == 2">
{{ t('common.closeList') }}
</a-button>
<ValidatePopover ref="validatePopoverRef" :errorList="errorList" @select="handleSelect" v-if="activeStep == 1" />
<a-space-compact block>
<a-button shape="round" @click="handlePrev" :disabled="activeStep <= 0 || btnLoading">{{ t('common.prev') }}</a-button>
<a-button shape="round" @click="handleNext" :disabled="activeStep >= maxStep || loading || btnLoading">{{ t('common.next') }} </a-button>
</a-space-compact>
<a-button shape="round" type="primary" @click="handleSubmit()" :disabled="loading" :loading="btnLoading">{{ t('common.saveText') }}</a-button>
<a-button shape="round" @click="handleCancel()">{{ t('common.closeText') }}</a-button>
</a-space>
</div>
</template>
<a-row type="flex" justify="center" align="middle" class="basic-content" v-show="!activeStep">
<a-col :span="12" :xxl="10" class="basic-form">
<BasicForm @register="registerForm" />
<a-table :data-source="tables" :columns="columns" size="small" :pagination="false" :scroll="{ x: 'max-content' }">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'typeId'">
<a-tag color="processing" v-if="record.typeId == '1'">主表</a-tag>
<a-tag color="warning" @click="changeTable(record)" v-else style="cursor: pointer" title="点击设置成主表">从表</a-tag>
</template>
<template v-if="column.key === 'table'">
<span :title="record.tableName || record.table">{{ record.table }}</span>
</template>
<template v-if="column.key === 'tableField' && record.typeId !== '1'">
<yunzhupaas-select
v-model:value="record.tableField"
placeholder="请选择"
:options="record.fields"
:field-names="{ value: 'field', label: 'field' }"
showSearch
class="!w-144px" />
</template>
<template v-if="column.key === 'relationField' && record.typeId !== '1'">
<yunzhupaas-select
v-model:value="record.relationField"
placeholder="请选择"
:options="mainTableFields"
:field-names="{ value: 'field', label: 'field' }"
showSearch
class="!w-144px" />
</template>
<template v-if="column.key === 'action'">
<a-button class="action-btn" type="link" color="error" @click="handleDelItem(record, index)" size="small">移除</a-button>
</template>
</template>
<template #emptyText>
<p class="ant-table__empty-text">点击新增可选择1条(单表)或2条以上(多表)未选择数据表时系统将会自动创建数据表</p>
</template>
</a-table>
<div class="table-add-action" @click="openTableBox">
<a-button type="link" preIcon="icon-ym icon-ym-btn-add">新增一行</a-button>
</div>
</a-col>
</a-row>
<FormGenerator
ref="generatorRef"
:conf="formData"
:formInfo="dataForm"
:dbType="dbType"
@showValidatePopover="showValidatePopover"
v-if="activeStep == 1" />
<BasicColumnDesign
ref="columnDesignRef"
:columnData="columnData"
:appColumnData="appColumnData"
:formInfo="dataForm"
@toggleWebType="toggleWebType"
v-if="activeStep == 2" />
<TableModal @register="registerTableModal" @select="onTableSelect" />
</BasicModal>
</template>
<script lang="ts" setup>
import { getInfo, create, update } from '@/api/onlineDev/visualDev';
import { getDataSourceSelector } from '@/api/systemData/dataSource';
import { getDataModelFieldList } from '@/api/systemData/dataModel';
import { ref, reactive, toRefs, unref, nextTick } from 'vue';
import { BasicModal, useModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useGeneratorStore } from '@/store/modules/generator';
import formValidate from '@/utils/formValidate';
import TableModal from './components/TableModal.vue';
import { FormGenerator } from '@/components/FormGenerator';
import { BasicColumnDesign } from '@/components/ColumnDesign';
import { ValidatePopover } from '@/components/CommonModal';
interface State {
activeStep: number;
maxStep: number;
loading: boolean;
btnLoading: boolean;
relationTable: boolean;
mainTableFields: any[];
dbOptions: any[];
tables: any[];
defaultTable: any[];
dataForm: Recordable;
isReload: boolean;
[prop: string]: any;
errorList: any[];
}
interface ComType {
getData: () => any;
setVisible: (data) => any;
activeFormItemById: (data) => any;
setTabActiveKey: (data) => any;
}
const emit = defineEmits(['register', 'reload']);
const [registerForm, { setFieldsValue, getFieldsValue, resetFields, validate, updateSchema }] = useForm({
schemas: [
{
field: 'fullName',
label: '表单名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 100 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '表单编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
},
{
field: 'category',
label: '表单分类',
component: 'Select',
componentProps: { placeholder: '请选择', showSearch: true },
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
{
field: 'sortCode',
label: '表单排序',
defaultValue: 0,
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
},
{
field: 'description',
label: '表单说明',
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
{
field: 'dbLinkId',
label: '数据连接',
defaultValue: '0',
component: 'Select',
componentProps: { placeholder: '请选择', allowClear: false, showSearch: true, fieldNames: { options: 'children' }, onChange: onDbChange },
},
],
});
const [registerTableModal, { openModal: openTableModal }] = useModal();
const [registerModal, { closeModal, changeLoading }] = useModalInner(init);
const { createMessage, createConfirm } = useMessage();
const generatorStore = useGeneratorStore();
const { t } = useI18n();
const state = reactive<State>({
activeStep: 0,
maxStep: 2,
loading: false,
btnLoading: false,
relationTable: false,
mainTableFields: [],
dbOptions: [],
tables: [],
defaultTable: [],
dataForm: {
id: '',
fullName: '',
enCode: '',
type: 1,
webType: 2,
dbLinkId: '0',
sortCode: 0,
state: 1,
category: '',
description: '',
tables: '',
interfaceId: '',
interfaceName: '',
interfaceParam: '',
},
formData: null,
columnData: null,
appColumnData: null,
dbType: 'MySQL',
isReload: false,
errorList: [],
});
const generatorRef = ref<Nullable<ComType>>(null);
const columnDesignRef = ref<Nullable<ComType>>(null);
const validatePopoverRef = ref<Nullable<ComType>>(null);
const { activeStep, maxStep, loading, btnLoading, tables, mainTableFields, dbType, formData, columnData, appColumnData, dataForm, errorList } = toRefs(state);
const columns = [
{ title: '类别', dataIndex: 'typeId', key: 'typeId', width: 65 },
{ title: '表名', dataIndex: 'table', key: 'table' },
{ title: '外键字段', dataIndex: 'tableField', key: 'tableField', width: 160 },
{ title: '关联主键', dataIndex: 'relationField', key: 'relationField', width: 160 },
{ title: '操作', dataIndex: 'action', key: 'action', width: 50, fixed: 'right' },
];
function handleDelItem(record, index) {
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '确定要移除当前行?',
onOk: () => {
state.tables.splice(index, 1);
if (record.typeId == '1' && state.tables.length) {
state.tables[0].typeId = '1';
state.tables[0].relationTable = '';
state.tables[0].tableField = '';
state.tables[0].relationField = '';
state.tables[0].relationTable = '';
state.mainTableFields = state.tables[0].fields;
state.relationTable = state.tables[0].table;
}
},
});
}
function init(data) {
state.isReload = false;
state.activeStep = 0;
state.loading = true;
state.tables = [];
state.defaultTable = [];
state.errorList = [];
state.formData = null;
state.columnData = null;
state.appColumnData = null;
updateSchema([{ field: 'category', componentProps: { options: data.categoryList } }]);
getDbOptions();
changeLoading(true);
resetFields();
state.dataForm.id = data.id;
if (state.dataForm.id) {
getInfo(state.dataForm.id).then(res => {
state.dataForm = res.data;
state.maxStep = state.dataForm.webType == 4 ? 1 : 2;
setFieldsValue(state.dataForm);
state.formData = state.dataForm.formData && JSON.parse(state.dataForm.formData);
state.columnData = state.dataForm.columnData && JSON.parse(state.dataForm.columnData);
state.appColumnData = state.dataForm.appColumnData && JSON.parse(state.dataForm.appColumnData);
state.tables = state.dataForm.tables ? JSON.parse(state.dataForm.tables) : [];
state.defaultTable = state.dataForm.tables ? JSON.parse(state.dataForm.tables) : [];
updateFields();
changeLoading(false);
});
} else {
state.dataForm.type = data.type;
state.dataForm.webType = data.webType || 2;
state.maxStep = state.dataForm.webType == 4 ? 1 : 2;
state.loading = false;
changeLoading(false);
}
}
function toggleWebType(type) {
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: type == '1' ? '关闭后,将切换为纯表单模式' : '开启后,将切换为表单+列表模式',
onOk: () => {
state.dataForm.webType = type;
},
});
}
async function updateFields() {
if (!state.tables.length) {
state.loading = false;
nextTick(() => handleNext());
return;
}
state.dataForm.dbLinkId = state.dataForm.dbLinkId || '0';
const type = state.dataForm.type;
const queryType = type == 3 || type == 4 || type == 5 ? '1' : '0';
for (let i = 0; i < state.tables.length; i++) {
const res = await getDataModelFieldList(state.dataForm.dbLinkId, state.tables[i].table, queryType);
const fields = res.data.list;
state.tables[i].fields = fields;
if (state.tables[i].typeId == '1') {
state.mainTableFields = state.tables[i].fields;
state.relationTable = state.tables[i].table;
}
}
state.loading = false;
nextTick(() => handleNext());
}
function onDbChange() {
state.tables = [];
}
function getDbOptions() {
getDataSourceSelector().then(res => {
let list = res.data.list || [];
list = list.filter(o => o.children && o.children.length);
if (list[0] && list[0].children && list[0].children.length) list[0] = list[0].children[0];
delete list[0].children;
state.dbOptions = list;
updateSchema([{ field: 'dbLinkId', componentProps: { options: state.dbOptions } }]);
});
}
function getDbType() {
for (let i = 0; i < state.dbOptions.length; i++) {
const item = state.dbOptions[i];
if (state.dataForm.dbLinkId === item.id) {
state.dbType = item.dbType;
break;
}
const e = state.dbOptions[i].children || [];
for (let j = 0; j < e.length; j++) {
if (state.dataForm.dbLinkId === e[j].id) {
state.dbType = e[j].dbType;
break;
}
}
}
}
function openTableBox() {
const values = getFieldsValue();
if (!values.dbLinkId) return createMessage.error('请先选择数据库');
openTableModal(true, { dbLinkId: values.dbLinkId });
}
async function onTableSelect(data) {
const values = getFieldsValue();
const type = state.dataForm.type;
const queryType = type == 3 || type == 4 || type == 5 ? '1' : '0';
const checkList: any[] = [];
if (!state.tables.length) {
for (let i = 0; i < data.length; i++) {
const e = data[i];
const relationTable = data[0].table;
const typeId = i == 0 ? '1' : '0';
const res = await getDataModelFieldList(values.dbLinkId, e.table, queryType);
const fields = res.data.list;
const item = {
relationField: '',
relationTable: i == 0 ? '' : relationTable,
table: e.table,
tableName: e.tableName,
tableField: '',
typeId,
fields,
};
checkList.push(item);
}
state.relationTable = checkList[0].table;
state.mainTableFields = checkList[0].fields;
state.tables = checkList;
} else {
for (let i = 0; i < data.length; i++) {
const e = data[i];
let boo = state.tables.some(o => o.table == e.table);
if (!boo) {
const res = await getDataModelFieldList(values.dbLinkId, e.table, queryType);
const fields = res.data.list;
const item = {
relationField: '',
relationTable: state.relationTable,
table: e.table,
tableName: e.tableName,
tableField: '',
typeId: '0',
fields,
};
checkList.push(item);
}
}
state.tables = [...state.tables, ...checkList];
}
state.loading = false;
}
function changeTable(record) {
state.relationTable = record.table;
state.mainTableFields = record.fields;
for (let i = 0; i < state.tables.length; i++) {
state.tables[i].typeId = state.tables[i].table === record.table ? '1' : '0';
state.tables[i].relationTable = state.tables[i].table === record.table ? '' : state.relationTable;
state.tables[i].relationField = '';
state.tables[i].tableField = '';
}
}
function handlePrev() {
state.activeStep -= 1;
if (state.activeStep == 0) updateTables();
}
async function handleNext() {
if (state.activeStep < 1) {
const values = await validate();
if (!values) return;
state.dataForm = { ...state.dataForm, ...values };
getDbType();
const type = state.dataForm.type;
if (!state.tables.length) {
if (state.defaultTable.length || type == 3 || type == 4) {
createMessage.warning('请至少选择一个数据表');
return;
}
generatorStore.setHasTable(false);
generatorStore.setAllTable([]);
generatorStore.setFormItemList([]);
state.activeStep += 1;
} else {
if (!exist()) return;
const subTable = state.tables.filter(o => o.typeId == '0');
generatorStore.setHasTable(true);
generatorStore.setAllTable(state.tables);
generatorStore.setSubTable(subTable);
generatorStore.setFormItemList(state.mainTableFields);
state.activeStep += 1;
}
} else if (state.activeStep === 1) {
(unref(generatorRef) as ComType)
.getData()
.then(res => {
state.errorList = res.errorList || [];
if (state.errorList.length) {
setTimeout(() => {
unref(validatePopoverRef)?.setVisible(true);
}, 10);
return;
}
state.formData = res.formData;
state.dataForm.formData = state.formData ? JSON.stringify(state.formData) : null;
state.activeStep += 1;
})
.catch(err => {
err.msg && createMessage.warning(err.msg);
});
} else {
(unref(columnDesignRef) as ComType)
.getData()
.then(res => {
state.columnData = res.columnData;
state.appColumnData = res.appColumnData;
state.activeStep += 1;
})
.catch(err => {
err.msg && createMessage.warning(err.msg);
});
}
}
function onStepChange(current) {
if (current == 0) updateTables();
}
function updateTables() {
state.tables = generatorStore.getAllTable;
state.mainTableFields = generatorStore.getFormItemList;
}
function exist() {
let isOk = true;
for (let i = 0; i < state.tables.length; i++) {
const e = state.tables[i];
if (e.typeId == '0') {
if (!e.tableField) {
createMessage.warning(`${e.table}外键字段不能为空`);
isOk = false;
break;
}
if (!e.relationField) {
createMessage.warning(`${e.table}关联主键不能为空`);
isOk = false;
break;
}
}
}
return isOk;
}
async function handleSubmit() {
if (state.activeStep < 1) {
const type = state.dataForm.type;
if (!state.tables.length && (state.defaultTable.length || type == 3 || type == 4)) return createMessage.warning('请至少选择一个数据表');
const values = await validate();
if (!values) return;
state.dataForm = { ...state.dataForm, ...values };
handleRequest();
} else if (state.activeStep === 1) {
(unref(generatorRef) as ComType)
.getData()
.then(res => {
state.errorList = res.errorList || [];
if (state.errorList.length) {
setTimeout(() => {
unref(validatePopoverRef)?.setVisible(true);
}, 10);
return;
}
state.formData = res.formData;
state.dataForm.formData = state.formData ? JSON.stringify(state.formData) : null;
handleRequest();
})
.catch(err => {
err.msg && createMessage.warning(err.msg);
});
} else {
if (state.dataForm.webType == 1) return handleRequest();
(unref(columnDesignRef) as ComType)
.getData()
.then(res => {
state.columnData = res.columnData;
state.appColumnData = res.appColumnData;
handleRequest();
})
.catch(err => {
err.msg && createMessage.warning(err.msg);
});
}
}
function handleRequest() {
state.btnLoading = true;
const query = {
...state.dataForm,
tables: JSON.stringify(state.tables),
formData: state.formData ? JSON.stringify(state.formData) : null,
columnData: state.columnData ? JSON.stringify(state.columnData) : null,
appColumnData: state.appColumnData ? JSON.stringify(state.appColumnData) : null,
};
const formMethod = state.dataForm.id ? update : create;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
state.btnLoading = false;
state.isReload = true;
if (!state.dataForm.id) state.dataForm.id = res.data;
})
.catch(() => {
state.btnLoading = false;
});
}
function handleCancel() {
closeModal();
if (state.isReload) emit('reload');
}
function handleSelect(id) {
unref(generatorRef)?.setTabActiveKey(id == 'formAttr' ? 'form' : 'field');
unref(generatorRef)?.activeFormItemById(id);
}
function showValidatePopover(list) {
state.errorList = list || [];
if (state.errorList.length) {
setTimeout(() => {
unref(validatePopoverRef)?.setVisible(true);
}, 10);
}
}
</script>

View File

@@ -0,0 +1,387 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
defaultFullscreen
:footer="null"
:closable="false"
:keyboard="false"
class="yunzhupaas-full-modal full-modal designer-modal">
<template #title>
<div class="yunzhupaas-full-modal-header">
<div class="header-title">
<img src="@/assets/images/yunzhupaas.png" class="header-logo" />
<span class="header-dot"></span>
<p class="header-txt" v-if="!activeStep">在线开发</p>
<a-tooltip :title="dataForm.fullName" v-else>
<p class="header-txt">{{ dataForm.fullName }}</p>
</a-tooltip>
</div>
<a-steps v-model:current="activeStep" type="navigation" size="small" class="header-steps">
<a-step title="基础设计" />
<a-step title="列表设计" disabled />
</a-steps>
<a-space class="options" :size="10">
<a-space-compact block>
<a-button shape="round" @click="handlePrev" :disabled="activeStep <= 0 || btnLoading">{{ t('common.prev') }}</a-button>
<a-button shape="round" @click="handleNext" :disabled="activeStep >= maxStep || loading || btnLoading">{{ t('common.next') }} </a-button>
</a-space-compact>
<a-button shape="round" type="primary" @click="handleSubmit()" :disabled="loading" :loading="btnLoading">{{ t('common.saveText') }}</a-button>
<a-button shape="round" @click="handleCancel()">{{ t('common.closeText') }}</a-button>
</a-space>
</div>
</template>
<a-row type="flex" justify="center" align="middle" class="basic-content" v-show="!activeStep">
<a-col :span="12" :xxl="10" class="basic-form">
<BasicForm @register="registerForm">
<template #interfaceId="{ model, field }">
<interface-modal :value="model[field]" :title="dataForm.interfaceName" :sourceType="2" @change="onInterfaceChange" />
</template>
</BasicForm>
<a-table :data-source="interfaceParam" :columns="templateJsonColumns" size="small" :pagination="false" v-if="interfaceParam && interfaceParam.length">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'field'">
<span class="required-sign">{{ record.required ? '*' : '' }}</span>
{{ record.field }}{{ record.fieldName ? '(' + record.fieldName + ')' : '' }}
</template>
<template v-if="column.key === 'sourceType'">
<yunzhupaas-select
v-model:value="record.sourceType"
:options="getSourceTypeOptions(record.required)"
class="!w-100px"
:disabled="record.disabled"
@change="onSourceTypeChange(record)" />
</template>
<template v-if="column.key === 'relationField'">
<template v-if="record.sourceType == 2">
<yunzhupaas-input-number
v-model:value="record.relationField"
placeholder="请输入"
allowClear
:disabled="record.disabled"
v-if="record.dataType === 'int' || record.dataType === 'decimal'" />
<yunzhupaas-date-picker
class="!w-full"
v-model:value="record.relationField"
placeholder="请选择"
format="yyyy-MM-dd HH:mm:ss"
allowClear
:disabled="record.disabled"
v-else-if="record.dataType === 'datetime'" />
<a-input v-model:value="record.relationField" placeholder="请输入" allowClear :disabled="record.disabled" v-else />
</template>
<yunzhupaas-select
v-model:value="record.relationField"
placeholder="请选择"
:options="systemFieldsOptions"
:fieldNames="{ options: 'options1' }"
allowClear
class="!w-161px"
:disabled="record.disabled"
v-else-if="record.sourceType === 4" />
</template>
<template v-if="column.key === 'useSearch'">
<a-checkbox v-model:checked="record.useSearch" @change="onUseSearchChange($event, record)" />
</template>
</template>
</a-table>
</a-col>
</a-row>
<BasicColumnDesign
ref="columnDesignRef"
:columnData="columnData"
:appColumnData="appColumnData"
:formInfo="dataForm"
:viewFields="viewFields"
:interfaceParam="interfaceParam"
:interfaceHasPage="interfaceHasPage"
v-if="activeStep == 1" />
</BasicModal>
</template>
<script lang="ts" setup>
import { getInfo, create, update } from '@/api/onlineDev/visualDev';
import { ref, reactive, toRefs, unref } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import formValidate from '@/utils/formValidate';
import { BasicColumnDesign } from '@/components/ColumnDesign';
import { InterfaceModal } from '@/components/CommonModal';
import { getDataInterfaceInfo } from '@/api/systemData/dataInterface';
import { sourceTypeOptions, interfaceSystemOptions } from '@/components/FlowProcess/src/helper/define';
interface State {
activeStep: number;
maxStep: number;
loading: boolean;
btnLoading: boolean;
tables: any[];
defaultTable: any[];
dataForm: Recordable;
viewFields: any[];
interfaceParam: any[];
interfaceHasPage: number;
isReload: boolean;
[prop: string]: any;
}
interface ComType {
getData: () => any;
}
const emit = defineEmits(['register', 'reload']);
const [registerForm, { setFieldsValue, resetFields, validate, updateSchema, clearValidate }] = useForm({
schemas: [
{
field: 'fullName',
label: '视图名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 100 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '视图编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [
{ required: true, trigger: 'blur', message: '必填' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
},
{
field: 'category',
label: '视图分类',
component: 'Select',
componentProps: { placeholder: '请选择', showSearch: true },
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
{
field: 'sortCode',
label: '视图排序',
defaultValue: 0,
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
},
{
field: 'description',
label: '视图说明',
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
{
field: 'interfaceId',
label: '数据接口',
slot: 'interfaceId',
component: 'Select',
rules: [{ required: true, trigger: 'change', message: '必填' }],
},
],
});
const [registerModal, { closeModal, changeLoading }] = useModalInner(init);
const { createMessage } = useMessage();
const { t } = useI18n();
const state = reactive<State>({
activeStep: 0,
maxStep: 1,
loading: false,
btnLoading: false,
tables: [],
defaultTable: [],
dataForm: {
id: '',
fullName: '',
enCode: '',
type: 1,
webType: 4,
dbLinkId: '0',
sortCode: 0,
state: 1,
category: '',
description: '',
tables: '',
interfaceId: '',
interfaceName: '',
interfaceParam: '',
},
formData: null,
columnData: null,
appColumnData: null,
interfaceParam: [],
viewFields: [],
interfaceHasPage: 0,
isReload: false,
});
const columnDesignRef = ref<Nullable<ComType>>(null);
const { activeStep, maxStep, loading, btnLoading, columnData, appColumnData, dataForm, interfaceParam, viewFields, interfaceHasPage } = toRefs(state);
const templateJsonColumns = [
{ title: '序号', width: 50, align: 'center', customRender: ({ index }) => index + 1 },
{ title: '参数名称', dataIndex: 'field', key: 'field' },
{ title: '参数来源', dataIndex: 'sourceType', key: 'sourceType', width: 100 },
{ title: '参数值', dataIndex: 'relationField', key: 'relationField', width: 180 },
{ title: '作为查询字段', dataIndex: 'useSearch', key: 'useSearch', width: 110, align: 'center' },
];
const systemFieldsOptions = interfaceSystemOptions.filter(o => o.id != '@formId');
function init(data) {
state.isReload = false;
state.activeStep = 0;
state.loading = true;
state.tables = [];
state.defaultTable = [];
state.interfaceParam = [];
state.formData = null;
state.columnData = null;
state.appColumnData = null;
state.dataForm.interfaceId = '';
state.dataForm.interfaceName = '';
state.dataForm.interfaceParam = '';
updateSchema([{ field: 'category', componentProps: { options: data.categoryList } }]);
changeLoading(true);
resetFields();
state.dataForm.id = data.id;
if (state.dataForm.id) {
getInfo(state.dataForm.id).then(res => {
state.dataForm = res.data;
state.maxStep = state.dataForm.webType == 4 ? 1 : 2;
setFieldsValue(state.dataForm);
state.formData = state.dataForm.formData && JSON.parse(state.dataForm.formData);
state.columnData = state.dataForm.columnData && JSON.parse(state.dataForm.columnData);
state.appColumnData = state.dataForm.appColumnData && JSON.parse(state.dataForm.appColumnData);
state.tables = (state.dataForm.tables && JSON.parse(state.dataForm.tables)) || [];
state.defaultTable = (state.dataForm.tables && JSON.parse(state.dataForm.tables)) || [];
state.interfaceParam = state.dataForm.interfaceParam ? JSON.parse(state.dataForm.interfaceParam) : [];
if (state.dataForm.interfaceId) initInterface();
state.loading = false;
changeLoading(false);
});
} else {
state.dataForm.type = data.type;
state.dataForm.webType = 4;
state.maxStep = state.dataForm.webType == 4 ? 1 : 2;
state.loading = false;
changeLoading(false);
}
}
function initInterface() {
changeLoading(true);
getDataInterfaceInfo(state.dataForm.interfaceId).then(res => {
const data = res.data;
state.dataForm.interfaceName = data.fullName;
state.viewFields = data.fieldJson ? JSON.parse(data.fieldJson) : [];
state.interfaceHasPage = data.hasPage;
const parameterJson = (data.parameterJson ? JSON.parse(data.parameterJson) : []).map(o => ({ ...o, useSearch: false, disabled: false }));
for (let i = 0; i < parameterJson.length; i++) {
const findIndex = state.interfaceParam.findIndex(o => o.field === parameterJson[i].field);
if (findIndex > -1) parameterJson[i] = state.interfaceParam[findIndex];
}
state.interfaceParam = parameterJson;
changeLoading(false);
});
}
function onInterfaceChange(val, row) {
state.viewFields = [];
if (!val) {
state.dataForm.interfaceId = '';
state.dataForm.interfaceName = '';
state.interfaceParam = [];
state.dataForm.interfaceParam = '';
state.interfaceHasPage = 0;
setFieldsValue({
interfaceId: state.dataForm.interfaceId,
interfaceName: state.dataForm.interfaceName,
interfaceParam: state.dataForm.interfaceParam,
});
return;
}
state.viewFields = row.fieldJson ? JSON.parse(row.fieldJson) : [];
state.interfaceHasPage = row.hasPage;
if (state.dataForm.interfaceId === val) return;
state.dataForm.interfaceId = val;
state.dataForm.interfaceName = row.fullName;
state.interfaceParam = (row.parameterJson ? JSON.parse(row.parameterJson) : []).map(o => ({
...o,
useSearch: false,
sourceType: 2,
relationField: '',
disabled: false,
}));
state.dataForm.interfaceParam = JSON.stringify(state.interfaceParam);
setFieldsValue({
interfaceId: state.dataForm.interfaceId,
interfaceName: state.dataForm.interfaceName,
interfaceParam: state.dataForm.interfaceParam,
});
setTimeout(() => {
clearValidate('interfaceId');
}, 0);
}
function handlePrev() {
state.activeStep -= 1;
}
async function handleNext() {
if (state.activeStep < 1) {
const values = await validate();
if (!values) return;
if (!state.viewFields.length) return createMessage.error('请先设置数据接口的字段列表!');
state.dataForm = { ...state.dataForm, ...values };
state.activeStep += 1;
}
}
async function handleSubmit() {
if (state.activeStep < 1) {
const values = await validate();
if (!values) return;
if (!state.viewFields.length) return createMessage.error('请先设置数据接口的字段列表!');
state.dataForm = { ...state.dataForm, ...values };
handleRequest();
} else if (state.activeStep === 1) {
(unref(columnDesignRef) as ComType)
.getData()
.then(res => {
state.columnData = res.columnData;
state.appColumnData = res.appColumnData;
handleRequest();
})
.catch(err => {
err.msg && createMessage.warning(err.msg);
});
}
}
function handleRequest() {
state.btnLoading = true;
const query = {
...state.dataForm,
tables: JSON.stringify(state.tables),
formData: state.formData ? JSON.stringify(state.formData) : null,
columnData: state.columnData ? JSON.stringify(state.columnData) : null,
appColumnData: state.appColumnData ? JSON.stringify(state.appColumnData) : null,
interfaceParam: JSON.stringify(state.interfaceParam),
};
const formMethod = state.dataForm.id ? update : create;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
state.btnLoading = false;
state.isReload = true;
if (!state.dataForm.id) state.dataForm.id = res.data;
})
.catch(() => {
state.btnLoading = false;
});
}
function onUseSearchChange(e, record) {
record.sourceType = 2;
record.relationField = undefined;
record.disabled = e.target.checked;
}
function onSourceTypeChange(record) {
record.relationField = record.sourceType == 4 ? systemFieldsOptions[0]?.id : '';
}
function getSourceTypeOptions(isRequired) {
return isRequired ? sourceTypeOptions.filter(o => o.id != 3 && o.id != 1) : sourceTypeOptions.filter(o => o.id != 1);
}
function handleCancel() {
closeModal();
if (state.isReload) emit('reload');
}
</script>

View File

@@ -0,0 +1,142 @@
<template>
<BasicModal v-bind="getBindValue" @register="registerModal" @ok="handleSubmit" destroyOnClose>
<div class="!pb-35px">
<div class="text-tips">通过AI生成表单 根据文字描述智能生成推荐表单</div>
<div class="ai-textarea">
<yunzhupaas-textarea v-model:value="content" placeholder="输入你的需求描述" :maxlength="100" />
<div class="ai-button">
<loading-outlined class="mr-5px" v-if="loading" />
<i class="ym-custom ym-custom-send cursor-pointer" :class="{ 'icon-selected': content }" v-else @click="handleAiSubmit" />
</div>
</div>
</div>
<a-form :colon="false" :labelCol="{ style: { width: '77px' } }" :model="dataForm" :rules="rules" ref="formElRef" v-if="showForm">
<div class="text-tips">表单已生成请继续确认表单名称编码及分类</div>
<a-form-item label="表单名称" name="fullName">
<yunzhupaas-input v-model:value="dataForm.fullName" placeholder="请输入" :maxlength="100" />
</a-form-item>
<a-form-item label="表单编码" name="enCode">
<yunzhupaas-input v-model:value="dataForm.enCode" placeholder="请输入" :maxlength="50" />
</a-form-item>
<a-form-item label="表单分类" name="category">
<yunzhupaas-select v-model:value="dataForm.category" :options="categoryOptions" showSearch />
</a-form-item>
</a-form>
</BasicModal>
</template>
<script lang="ts" setup>
import { reactive, ref, toRefs, nextTick, computed, unref } from 'vue';
import { getAiInfo, create } from '@/api/onlineDev/visualDev';
import { BasicModal, useModalInner } from '@/components/Modal';
import type { FormInstance } from 'ant-design-vue';
import { LoadingOutlined } from '@ant-design/icons-vue';
import { useAttrs } from '@/hooks/core/useAttrs';
import formValidate from '@/utils/formValidate';
import { buildAiFormData } from '@/components/FormGenerator/src/helper/aiUtils';
import { omit } from 'lodash-es';
import { useMessage } from '@/hooks/web/useMessage';
interface State {
dataForm: any;
content: string;
loading: boolean;
categoryOptions: any[];
showForm: boolean;
}
const emit = defineEmits(['register', 'reload']);
const { createMessage } = useMessage();
const [registerModal, { changeOkLoading, closeModal }] = useModalInner(init);
const formElRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {},
content: '',
loading: false,
categoryOptions: [],
showForm: false,
});
const { dataForm, content, loading, categoryOptions, showForm } = toRefs(state);
const attrs = useAttrs({ excludeDefaultKeys: false });
const rules = {
fullName: [{ required: true, message: '必填', trigger: 'blur' }],
enCode: [
{ required: true, message: '必填', trigger: 'blur' },
{ validator: formValidate('enCode'), trigger: 'blur' },
],
category: [{ required: true, message: '必填', trigger: 'change' }],
};
const getBindValue = computed(() => {
const attr: any = {
...unref(attrs),
title: 'AI建表',
okButtonProps: {
disabled: state.loading,
},
};
if (!state.showForm) attr.footer = null;
return attr;
});
function init(data) {
state.categoryOptions = data.categoryList || [];
resetData();
nextTick(() => {
formElRef.value?.clearValidate();
});
}
function resetData() {
state.content = '';
state.loading = false;
state.showForm = false;
state.dataForm = {};
}
function handleAiSubmit() {
if (!state.content) return;
state.loading = true;
getAiInfo({ keyword: state.content })
.then(res => {
state.showForm = true;
state.loading = false;
state.dataForm = { ...res.data, category: state.dataForm.category };
})
.catch(() => {
state.loading = false;
});
}
async function handleSubmit() {
try {
const values = await formElRef.value?.validate();
if (!values) return;
state.dataForm = { ...buildAiFormData(state.dataForm.aiModelList || []), ...omit(state.dataForm, ['aiModelList']) };
changeOkLoading(true);
create(state.dataForm)
.then(res => {
changeOkLoading(false);
closeModal();
createMessage.success(res.msg);
emit('reload', res.data);
})
.catch(() => {
changeOkLoading(false);
});
} catch (_) {}
}
</script>
<style lang="less" scoped>
.ai-textarea {
position: relative;
.ai-button {
position: absolute;
bottom: 3px;
right: 8px;
}
}
.icon-selected {
color: @primary-color!important;
}
.text-tips {
margin: 10px 20px 20px 0;
color: @text-color-label;
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
defaultFullscreen
:footer="null"
:closable="false"
:keyboard="false"
class="yunzhupaas-full-modal full-modal designer-modal">
<template #title>
<div class="yunzhupaas-full-modal-header">
<div class="header-title">
<img src="@/assets/images/yunzhupaas.png" class="header-logo" />
<span class="header-dot"></span>
<a-tooltip :title="fullName">
<p class="header-txt">{{ fullName }}</p>
</a-tooltip>
</div>
<a-steps :current="1" type="navigation" size="small" class="header-steps tab-steps">
<a-step title="命名规范" />
</a-steps>
<a-space class="options" :size="10">
<a-button type="primary" @click="handleSubmit()" :disabled="loading" :loading="btnLoading">{{ t('common.saveText') }}</a-button>
<a-button @click="closeModal()">{{ t('common.closeText') }}</a-button>
</a-space>
</div>
</template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content bg-white p-10px">
<a-alert :message="tipTxt" :description="descTxt" type="warning" show-icon class="mb-10px" ref="alertElRef" />
<a-table :data-source="list" v-bind="getTableBindValues" v-model:expandedRowKeys="expandedRowKeys" ref="tableElRef">
<template #expandedRowRender="{ record }">
<a-table :data-source="record.fields" v-bind="getChildTableBindValues">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'field'">
<a-input v-model:value="record.field" disabled detailed />
</template>
<template v-if="column.key === 'aliasName'">
<a-input v-model:value="record.aliasName" placeholder="请输入" :maxlength="100" />
</template>
<template v-if="column.key === 'fieldName'">
<a-input v-model:value="record.fieldName" disabled detailed />
</template>
</template>
</a-table>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'table'">
<a-input v-model:value="record.table" disabled detailed />
</template>
<template v-if="column.key === 'aliasName'">
<a-input v-model:value="record.aliasName" placeholder="请输入" :maxlength="100" />
</template>
<template v-if="column.key === 'comment'">
<a-input v-model:value="record.comment" disabled detailed />
</template>
</template>
</a-table>
</div>
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { getAliasInfo, saveAlias } from '@/api/onlineDev/visualDev';
import { ref, reactive, toRefs, computed, unref, nextTick } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
interface State {
id: string;
fullName: string;
list: any[];
loading: boolean;
btnLoading: boolean;
expandedRowKeys: string[];
}
defineEmits(['register']);
const tipTxt = '规范名称命名规则1只能由字母、数字、下划线组成且不能以数字开头2不能使用系统关键字或开发语言关键字';
const descTxt =
'系统关键字包含tenantid、id、foreignid、flowid、flowtaskid、deleteuserid、deletetime、deletemark、version、tenant_id、foreign_id、flow_id、flow_task_id、delete_user_id、delete_time、delete_mark、f_tenant_id、f_id、f_foreign_id、f_flow_id、f_flow_task_id、f_delete_user_id、f_delete_time、f_delete_mark、f_version';
const { t } = useI18n();
const { createMessage } = useMessage();
const [registerModal, { closeModal, changeLoading }] = useModalInner(init);
const alertElRef = ref<any>(null);
const tableElRef = ref<any>(null);
const columns = [
{ width: 50, title: '序号', dataIndex: 'index', key: 'index', align: 'center', customRender: ({ index }) => index + 1 },
{ title: '表名', dataIndex: 'table', key: 'table' },
{ title: '规范名称', dataIndex: 'aliasName', key: 'aliasName' },
{ title: '说明', dataIndex: 'comment', key: 'comment' },
];
const childColumns = [
{ width: 50, title: '序号', dataIndex: 'index', key: 'index', align: 'center', customRender: ({ index }) => index + 1 },
{ title: '字段', dataIndex: 'field', key: 'field' },
{ title: '规范名称', dataIndex: 'aliasName', key: 'aliasName' },
{ title: '说明', dataIndex: 'fieldName', key: 'fieldName' },
];
const state = reactive<State>({
id: '',
fullName: '',
list: [],
loading: false,
btnLoading: false,
expandedRowKeys: [],
});
const { list, loading, btnLoading, expandedRowKeys, fullName } = toRefs(state);
const getHeaderHeight = computed(() => {
const alertEl = alertElRef.value?.$el;
if (!alertEl) return 120;
return alertEl?.offsetHeight;
});
const getScrollY = computed(() => window.innerHeight - unref(getHeaderHeight) - 150);
const getTableBindValues = computed(() => {
return {
columns,
pagination: false,
size: 'small',
rowKey: 'table',
scroll: { y: unref(getScrollY) },
};
});
const getChildTableBindValues = computed(() => {
return {
columns: childColumns,
pagination: false,
size: 'small',
rowKey: 'field',
};
});
function init(data) {
state.id = data.id;
state.fullName = data.fullName || '';
state.loading = true;
state.expandedRowKeys = [];
changeLoading(true);
getAliasInfo(data.id).then(res => {
state.list = res.data;
state.expandedRowKeys = res.data.map(e => e.table);
nextTick(() => {
const tableEl = tableElRef.value?.$el;
let bodyEl = tableEl.querySelector('.ant-table-body');
bodyEl!.style.height = `${unref(getScrollY)}px`;
changeLoading(false);
state.loading = false;
});
});
}
function handleSubmit() {
state.btnLoading = true;
changeLoading(true);
saveAlias(state.id, { tableList: state.list })
.then(res => {
state.btnLoading = false;
changeLoading(false);
createMessage.success(res.msg);
closeModal();
})
.catch(() => {
state.btnLoading = false;
changeLoading(false);
});
}
</script>

View File

@@ -0,0 +1,173 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="生成菜单" @ok="handleSubmit" class="yunzhupaas-release-modal">
<a-alert message="将该功能的按钮、列表、表单及数据权限发布至应用菜单" type="warning" show-icon />
<a-form class="release-main" :colon="false" :model="dataForm" :rules="rules" layout="vertical" ref="formElRef">
<div class="release-item">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.pc === 1 }" @click="selectToggle('pc')">
<i class="item-icon icon-ym icon-ym-pc"></i>
<p class="item-title">桌面端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="上级" name="pcModuleParentId" v-if="dataForm.pc">
<YunzhupaasTreeSelect
v-model:value="pcModuleParentId"
:options="treeData"
treeCheckStrictly
multiple
:dropdownMatchSelectWidth="false"
@change="onPcChange" />
</a-form-item>
<a-form-item label="已发布菜单路径" v-if="record.pcIsRelease">
<div class="released">{{ record.pcReleaseName }}</div>
</a-form-item>
</div>
<div class="release-item">
<a-form-item>
<div class="top-item" :class="{ active: dataForm.app === 1 }" @click="selectToggle('app')">
<i class="item-icon icon-ym icon-ym-mobile"></i>
<p class="item-title">移动端</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-form-item>
<a-form-item label="上级" name="appModuleParentId" v-if="dataForm.app">
<YunzhupaasTreeSelect
v-model:value="appModuleParentId"
:options="appTreeData"
treeCheckStrictly
multiple
:dropdownMatchSelectWidth="false"
@change="onAppChange" />
</a-form-item>
<a-form-item label="已发布菜单路径" v-if="record.appIsRelease">
<div class="released">{{ record.appReleaseName }}</div>
</a-form-item>
</div>
</a-form>
</BasicModal>
</template>
<script lang="ts" setup>
import { getReleaseMenu, createMenu } from '@/api/onlineDev/visualDev';
import { BasicModal, useModalInner } from '@/components/Modal';
import { ref, reactive, toRefs, computed } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import { getMenuSelectorFilter } from '@/api/system/menu';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
dataForm: any;
record: any;
treeData: any[];
appTreeData: any[];
pcModuleParentId: any[];
appModuleParentId: any[];
}
const emit = defineEmits(['register', 'reload']);
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const [registerModal, { changeOkLoading, closeModal }] = useModalInner(init);
const formElRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
pc: 1,
app: 1,
pcModuleParentId: [],
appModuleParentId: [],
},
record: {},
treeData: [],
appTreeData: [],
pcModuleParentId: [],
appModuleParentId: [],
});
const { dataForm, record, treeData, appTreeData, pcModuleParentId, appModuleParentId } = toRefs(state);
const rules = computed(() => {
let rules: any = {
pcModuleParentId: [],
appModuleParentId: [],
};
if (!state.record.pcIsRelease) rules.pcModuleParentId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
if (!state.record.appIsRelease) rules.appModuleParentId = [{ required: true, message: '必填', trigger: 'change', type: 'array' }];
return rules;
});
function init(data) {
state.pcModuleParentId = [];
state.appModuleParentId = [];
getReleaseMenu(data.id).then(res => {
state.record = res.data;
const platformRelease = res.data.platformRelease ? JSON.parse(res.data.platformRelease) : {};
state.dataForm = {
pc: platformRelease.pc === 0 ? 0 : 1,
app: platformRelease.app === 0 ? 0 : 1,
pcModuleParentId: [],
appModuleParentId: [],
};
formElRef.value?.clearValidate();
});
getMenuOptions(data.id);
getAppMenuOptions(data.id);
}
function getMenuOptions(id) {
getMenuSelectorFilter({ category: 'Web' }, id).then(res => {
state.treeData = res.data.list;
});
}
function getAppMenuOptions(id) {
getMenuSelectorFilter({ category: 'App' }, id).then(res => {
let list = res.data.list || [];
for (let index = 0; index < list.length; index++) {
const item = list[index];
if (item.type == 0) item.disabled = true;
}
state.appTreeData = list;
});
}
function onPcChange(data) {
state.dataForm.pcModuleParentId = data.map(o => o.value);
}
function onAppChange(data) {
state.dataForm.appModuleParentId = data.map(o => o.value);
}
function selectToggle(key) {
state.dataForm[key] = state.dataForm[key] === 1 ? 0 : 1;
}
async function handleSubmit() {
try {
if (!state.dataForm.pc && !state.dataForm.app) return createMessage.error('请至少选择一种发布类型');
const values = await formElRef.value?.validate();
if (!values) return;
const platform = { pc: state.dataForm.pc, app: state.dataForm.app };
const query = { ...state.dataForm, platformRelease: JSON.stringify(platform) };
const handleRelease = () => {
changeOkLoading(true);
createMenu(state.record.id, query)
.then(res => {
changeOkLoading(false);
createMessage.success(res.msg);
emit('reload');
closeModal();
})
.catch(() => {
changeOkLoading(false);
});
};
if (!state.record.isRelease) return handleRelease();
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '发布确定后会覆盖当前线上版本且进行菜单同步,是否继续?',
onOk: handleRelease,
});
} catch (_) {}
}
</script>

View File

@@ -0,0 +1,131 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
defaultFullscreen
:footer="null"
:closable="false"
:keyboard="false"
class="yunzhupaas-full-modal full-modal designer-modal">
<template #title>
<div class="yunzhupaas-full-modal-header">
<div class="header-title">
<img src="@/assets/images/yunzhupaas.png" class="header-logo" />
<span class="header-dot"></span>
<a-tooltip :title="fullName">
<p class="header-txt">{{ fullName }}</p>
</a-tooltip>
</div>
<a-steps :current="1" type="navigation" size="small" class="header-steps tab-steps">
<a-step title="代码预览" />
</a-steps>
<a-space class="options" :size="10">
<a-button @click="closeModal()">{{ t('common.closeText') }}</a-button>
</a-space>
</div>
</template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-left">
<BasicLeftTree :showSearch="false" ref="leftTreeRef" :fieldNames="{ title: 'fileName' }" :treeData="treeData" @select="handleTreeSelect" />
</div>
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content bg-white">
<!-- <code-diff-->
<!-- :old-string="oldFileContent"-->
<!-- :new-string="currentContent"-->
<!-- output-format="side-by-side"-->
<!-- :language="editorLanguage"-->
<!-- :theme="getThemeColor"-->
<!-- :context="99999"-->
<!-- :key="key" />-->
</div>
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { codePreview } from '@/api/onlineDev/visualDev';
import { reactive, toRefs, nextTick, ref, unref, computed } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicLeftTree, TreeActionType } from '@/components/Tree';
// import { CodeDiff } from 'v-code-diff';
import { useAppStore } from '@/store/modules/app';
interface State {
treeData: any[];
currentId: string;
oldFileContent: string;
currentContent: string;
editorLanguage: string;
key: number;
fullName: string;
}
defineEmits(['register']);
const { t } = useI18n();
const leftTreeRef = ref<Nullable<TreeActionType>>(null);
const appStore = useAppStore();
const [registerModal, { closeModal, changeLoading }] = useModalInner(init);
const state = reactive<State>({
treeData: [],
currentId: '',
oldFileContent: '',
currentContent: '',
editorLanguage: 'html',
key: +new Date(),
fullName: '',
});
const { treeData, oldFileContent, currentContent, editorLanguage, key, fullName } = toRefs(state);
const getThemeColor = computed(() => appStore.getDarkMode);
function init(data) {
state.fullName = data.fullName || '';
state.key = +new Date();
changeLoading(true);
const query = {
module: data.module || 'system',
description: data.description,
modulePackageName: data.modulePackageName || '',
enableFlow: data.enableFlow,
contrast: true,
};
codePreview(data.id, query).then(res => {
state.treeData = res.data.list.map(o => ({ ...o, disabled: true }));
state.currentId = state.treeData[0].children[0].id;
state.currentContent = state.treeData[0].children[0].fileContent;
state.oldFileContent = state.treeData[0].children[0].oldFileContent;
state.editorLanguage = getLanguage(state.treeData[0].children[0]);
nextTick(() => {
const leftTree = unref(leftTreeRef);
leftTree?.setSelectedKeys([state.currentId]);
changeLoading(false);
});
});
}
function handleTreeSelect(id, node) {
state.key = +new Date();
state.currentId = id;
state.currentContent = node.fileContent;
state.oldFileContent = node.oldFileContent;
state.editorLanguage = getLanguage(node);
}
function getLanguage(node) {
return ['web', 'app'].includes(node.fileType) ? 'html' : ['js', 'ts'].includes(node.folderName) ? 'js' : 'java';
}
</script>
<style lang="less">
.code-diff-view {
margin-top: unset !important;
margin-bottom: unset !important;
height: 100% !important;
border: unset !important;
background-color: @component-background!important;
.file-header,
.empty-cell {
background-color: @component-background!important;
}
}
</style>

View File

@@ -0,0 +1,117 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="输出设置" okText="下载代码" @ok="handleSubmit">
<template #insertFooter>
<a-space :size="10" class="float-left">
<a-button @click="handlePreview">代码预览</a-button>
<a-button @click="handleAlias" v-if="webType != 4">命名规范</a-button>
</a-space>
</template>
<a-alert message="注意:以下只能包含数字、字母、下划线、小圆点,不能用数字开头,不能是关键字或保留字。" type="warning" showIcon class="!mb-18px" />
<a-form :colon="false" :labelCol="{ style: { width: '100px' } }" :model="dataForm" ref="formElRef">
<a-form-item label="模块命名" name="module" v-if="type != 3" :rules="[{ required: true, message: '必填', trigger: 'change' }]">
<yunzhupaas-select v-model:value="dataForm.module" :options="moduleOptions" placeholder="请选择" showSearch />
</a-form-item>
<a-form-item name="modulePackageName" :rules="[{ required: true, message: '必填', trigger: 'blur' }]" v-if="hasPackage">
<template #label>模块包名<BasicHelp text="修改包名需要调整controller和mapper扫描配置" /></template>
<a-input v-model:value="dataForm.modulePackageName" placeholder="请输入" />
</a-form-item>
<a-form-item label="功能描述" name="description" :rules="[{ required: true, message: '必填', trigger: 'blur' }]">
<a-input v-model:value="dataForm.description" placeholder="请输入" />
</a-form-item>
<a-form-item label="流程模板" name="enableFlow" v-if="webType != 4">
<yunzhupaas-switch v-model:value="dataForm.enableFlow" />
</a-form-item>
</a-form>
</BasicModal>
<PreviewModal @register="registerPreviewModal" />
<AliasModal @register="registerAliasModal" />
</template>
<script lang="ts" setup>
import { reactive, ref, toRefs, nextTick } from 'vue';
import { downloadCode } from '@/api/onlineDev/visualDev';
import { BasicModal, useModal, useModalInner } from '@/components/Modal';
import type { FormInstance } from 'ant-design-vue';
import { downloadByUrl } from '@/utils/file/download';
import { useBaseStore } from '@/store/modules/base';
import PreviewModal from './PreviewModal.vue';
import AliasModal from './AliasModal.vue';
interface State {
dataForm: any;
moduleOptions: any[];
tables: any[];
type: number;
id: string;
hasPackage: boolean;
fullName: string;
webType: number;
}
defineEmits(['register']);
const baseStore = useBaseStore();
const [registerModal, { changeOkLoading, closeModal }] = useModalInner(init);
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const [registerAliasModal, { openModal: openAliasModal }] = useModal();
const formElRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
module: '',
modulePackageName: 'yunzhupaas',
description: '',
enableFlow: 0,
},
moduleOptions: [],
tables: [],
type: 0,
id: '',
hasPackage: false,
fullName: '',
webType: 2,
});
const { dataForm, type, moduleOptions, hasPackage, webType } = toRefs(state);
function init(data) {
state.tables = data.tables ? JSON.parse(data.tables) : [];
state.id = data.id;
state.type = data.type || 0;
state.hasPackage = !!data.hasPackage;
state.fullName = data.fullName || '';
state.webType = data.webType || 2;
state.dataForm.description = '';
state.dataForm.enableFlow = 0;
getOptions();
nextTick(() => {
formElRef.value?.clearValidate();
if (data.webType == 4) return;
const mainTable = state.tables.length ? state.tables.filter(o => o.typeId == '1')[0].table : '';
state.dataForm.description = mainTable;
});
}
function getOptions() {
baseStore.getDictionaryData('createModule').then(res => {
state.moduleOptions = res as any;
if (state.moduleOptions.length) state.dataForm.module = state.moduleOptions[0].id;
});
}
async function handleSubmit() {
try {
const values = await formElRef.value?.validate();
if (!values) return;
changeOkLoading(true);
downloadCode(state.id, state.dataForm)
.then(res => {
downloadByUrl({ url: res.data.url });
closeModal();
})
.catch(() => {
changeOkLoading(false);
});
} catch (_) {}
}
function handlePreview() {
openPreviewModal(true, { id: state.id, fullName: state.fullName, ...state.dataForm });
}
function handleAlias() {
openAliasModal(true, { id: state.id, fullName: state.fullName });
}
</script>

View File

@@ -0,0 +1,116 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
defaultFullscreen
:footer="null"
:closable="false"
:keyboard="false"
class="yunzhupaas-full-modal full-modal designer-modal">
<template #title>
<div class="yunzhupaas-full-modal-header">
<div class="header-title">
<img src="@/assets/images/yunzhupaas.png" class="header-logo" />
<span class="header-dot"></span>
<a-tooltip :title="fullName">
<p class="header-txt">{{ fullName }}</p>
</a-tooltip>
</div>
<a-steps :current="1" type="navigation" size="small" class="header-steps tab-steps">
<a-step title="代码预览" />
</a-steps>
<a-space class="options" :size="10">
<a-button @click="openDiffPreviewModal(true, { ...data })">代码对比</a-button>
<a-button @click="closeModal()">{{ t('common.closeText') }}</a-button>
</a-space>
</div>
</template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-left">
<BasicLeftTree :showSearch="false" ref="leftTreeRef" :fieldNames="{ title: 'fileName' }" :treeData="treeData" @select="handleTreeSelect" />
</div>
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content bg-white">
<MonacoEditor v-model="currentContent" :options="editorOptions" :language="editorLanguage" :key="key" />
</div>
</div>
</div>
</BasicModal>
<DiffPreviewModal @register="registerDiffPreviewModal" />
</template>
<script lang="ts" setup>
import { codePreview } from '@/api/onlineDev/visualDev';
import { reactive, toRefs, nextTick, ref, unref } from 'vue';
import { BasicModal, useModal, useModalInner } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicLeftTree, TreeActionType } from '@/components/Tree';
import { MonacoEditor } from '@/components/CodeEditor';
import DiffPreviewModal from './DiffPreviewModal.vue';
interface State {
treeData: any[];
currentId: string;
currentContent: string;
editorOptions: any;
editorLanguage: string;
key: number;
fullName: string;
data: any;
}
defineEmits(['register']);
const { t } = useI18n();
const leftTreeRef = ref<Nullable<TreeActionType>>(null);
const [registerModal, { closeModal, changeLoading }] = useModalInner(init);
const state = reactive<State>({
treeData: [],
currentId: '',
currentContent: '',
editorOptions: {
readOnly: true,
scrollBeyondLastLine: true,
minimap: {
enabled: true,
},
},
editorLanguage: 'html',
key: +new Date(),
fullName: '',
data: {},
});
const { treeData, currentContent, editorOptions, editorLanguage, key, fullName, data } = toRefs(state);
const [registerDiffPreviewModal, { openModal: openDiffPreviewModal }] = useModal();
function init(data) {
state.fullName = data.fullName || '';
state.data = data;
state.treeData = [];
state.currentId = '';
state.currentContent = '';
state.key = +new Date();
changeLoading(true);
const query = {
module: data.module || 'system',
description: data.description,
modulePackageName: data.modulePackageName || '',
enableFlow: data.enableFlow,
};
codePreview(data.id, query).then(res => {
state.treeData = res.data.list.map(o => ({ ...o, disabled: true }));
state.currentId = state.treeData[0].children[0].id;
state.currentContent = state.treeData[0].children[0].fileContent;
state.editorLanguage = ['web', 'app'].includes(state.treeData[0].children[0].fileType) ? 'html' : 'java';
nextTick(() => {
const leftTree = unref(leftTreeRef);
leftTree?.setSelectedKeys([state.currentId]);
changeLoading(false);
});
});
}
function handleTreeSelect(id, node) {
state.key = +new Date();
state.currentId = id;
state.currentContent = node.fileContent;
state.editorLanguage = ['web', 'app'].includes(node.fileType) ? 'html' : 'java';
}
</script>

View File

@@ -0,0 +1,380 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="表单外链配置" :width="800" @ok="handleSubmit" class="yunzhupaas-shortLink-modal">
<a-form :colon="false" :model="dataForm" :rules="rules" :labelCol="{ style: { width: '100px' } }" ref="formElRef" hideRequiredMark class="h-500px">
<a-form-item label="外链填单" name="formUse">
<a-space :size="15">
<YunzhupaasSwitch v-model:value="dataForm.formUse" />
<span class="shortLink-tip">开启后会生成表单填写链接无需登录即可填写表单</span>
</a-space>
</a-form-item>
<template v-if="dataForm.formUse">
<a-form-item label="外链地址">
<a-space :size="15">
<a-input v-model:value="dataForm.formLink" readonly class="!w-410px" />
<template v-if="dataForm.alreadySave">
<a-input-group compact>
<a-button @click="openWindow(dataForm.formLink)">打开</a-button>
<a-button type="primary" @click="handleCopy(dataForm.formLink)">复制</a-button>
</a-input-group>
<a-popover placement="bottomRight">
<template #content>
<p class="shortLink-tip">扫描二维码分享此链接</p>
<QrCode :value="dataForm.formLink" ref="qrRef" :width="154" :options="{ margin: 1 }" class="my-5px" />
<a-button pre-icon="icon-ym icon-ym-download" block size="mini" @click="handleDownload(1)">下载</a-button>
</template>
<i class="ym-custom ym-custom-qrcode"></i>
</a-popover>
</template>
<template v-else>
<a-input-group compact>
<a-tooltip placement="bottom">
<template #title>请先保存再打开</template>
<a-button disabled>打开</a-button>
</a-tooltip>
<a-tooltip placement="bottom">
<template #title>请先保存再复制</template>
<a-button type="primary" disabled>复制</a-button>
</a-tooltip>
</a-input-group>
<a-tooltip placement="bottom">
<template #title>请先保存再下载二维码</template>
<i class="ym-custom ym-custom-qrcode"></i>
</a-tooltip>
</template>
</a-space>
</a-form-item>
<a-row justify="start">
<a-col>
<a-form-item label="凭密码填写" name="formPassUse">
<YunzhupaasSwitch v-model:value="dataForm.formPassUse" />
</a-form-item>
</a-col>
<a-col class="ml-15px">
<a-form-item name="formPassword" v-if="dataForm.formPassUse">
<a-input-password v-model:value="dataForm.formPassword" class="!w-200px" placeholder="请输入密码" :maxlength="20" />
</a-form-item>
</a-col>
</a-row>
<div></div>
</template>
<template v-if="webType != '1'">
<a-divider></a-divider>
<a-form-item label="公开查询" name="columnUse">
<a-space :size="15">
<YunzhupaasSwitch v-model:value="dataForm.columnUse" />
<span class="shortLink-tip">开启后无需登录即可查询已有数据</span>
</a-space>
</a-form-item>
<template v-if="dataForm.columnUse">
<a-form-item label="查询地址">
<a-space :size="15">
<a-input v-model:value="dataForm.columnLink" readonly class="!w-410px" />
<template v-if="dataForm.alreadySave">
<a-input-group compact>
<a-button @click="openWindow(dataForm.columnLink)">打开</a-button>
<a-button type="primary" @click="handleCopy(dataForm.columnLink)">复制</a-button>
</a-input-group>
<a-popover placement="bottomRight">
<template #content>
<p class="shortLink-tip">扫描二维码分享此链接</p>
<QrCode :value="dataForm.columnLink" ref="columnQrRef" :width="154" :options="{ margin: 1 }" class="my-5px" />
<a-button pre-icon="icon-ym icon-ym-download" block size="mini" @click="handleDownload(2)">下载</a-button>
</template>
<i class="ym-custom ym-custom-qrcode"></i>
</a-popover>
</template>
<template v-else>
<a-input-group compact>
<a-tooltip placement="bottom">
<template #title>请先保存再打开</template>
<a-button disabled>打开</a-button>
</a-tooltip>
<a-tooltip placement="bottom">
<template #title>请先保存再复制</template>
<a-button type="primary" disabled>复制</a-button>
</a-tooltip>
</a-input-group>
<a-tooltip placement="bottom">
<template #title>请先保存再下载二维码</template>
<i class="ym-custom ym-custom-qrcode"></i>
</a-tooltip>
</template>
</a-space>
</a-form-item>
<a-form-item label="查询条件" name="columnCondition">
<YunzhupaasSelect
v-model:value="columnCondition"
placeholder="请选择"
:options="searchOptions"
:fieldNames="{ options: 'options1' }"
allowClear
showSearch
multiple
@change="onConditionChange" />
</a-form-item>
<a-form-item label="显示内容" name="columnText">
<YunzhupaasSelect
v-model:value="columnText"
placeholder="请选择"
:options="columnOptions"
:fieldNames="{ options: 'options1' }"
allowClear
showSearch
multiple
@change="onColumnTextChange" />
</a-form-item>
<a-row justify="start">
<a-col>
<a-form-item label="凭密码填写" name="columnPassUse">
<YunzhupaasSwitch v-model:value="dataForm.columnPassUse" />
</a-form-item>
</a-col>
<a-col class="ml-15px">
<a-form-item name="columnPassword" v-if="dataForm.columnPassUse">
<a-input-password v-model:value="dataForm.columnPassword" class="!w-200px" placeholder="请输入密码" :maxlength="20" />
</a-form-item>
</a-col>
</a-row>
</template>
</template>
</a-form>
</BasicModal>
</template>
<script lang="ts" setup>
import { getShortLinkInfo, updateShortLink } from '@/api/onlineDev/shortLink';
import { getInfo } from '@/api/onlineDev/visualDev';
import { BasicModal, useModalInner } from '@/components/Modal';
import { ref, reactive, toRefs, nextTick, unref } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { useMessage } from '@/hooks/web/useMessage';
import { noColumnShowList, noSearchList } from '@/components/ColumnDesign/src/helper/config';
import { QrCode, QrCodeActionType } from '@/components/Qrcode/index';
import { openWindow } from '@/utils';
import { useCopyToClipboard } from '@/hooks/web/useCopyToClipboard';
interface State {
dataForm: any;
record: any;
rules: any;
columnCondition: any[];
columnText: any[];
columnConditionData: any[];
columnTextData: any[];
columnOptions: any[];
searchOptions: any[];
webType: string;
id: string;
}
const validFieldsList = [
'input',
'textarea',
'inputNumber',
'switch',
'datePicker',
'timePicker',
'colorPicker',
'rate',
'slider',
'editor',
'link',
'text',
'alert',
'table',
'collapse',
'collapseItem',
'tabItem',
'tab',
'row',
'card',
'groupTitle',
'divider',
'tableGrid',
'tableGridTr',
'tableGridTd',
'location',
'steps',
'stepItem',
];
const selectFieldsList = ['radio', 'checkbox', 'select', 'treeSelect', 'cascader'];
defineEmits(['register']);
const { createMessage } = useMessage();
const [registerModal, { changeLoading, changeOkLoading }] = useModalInner(init);
const formElRef = ref<FormInstance>();
const qrRef = ref<Nullable<QrCodeActionType>>(null);
const columnQrRef = ref<Nullable<QrCodeActionType>>(null);
const state = reactive<State>({
dataForm: {
id: '',
formLink: '',
formUse: 0,
formPassUse: 0,
formPassword: '',
columnUse: 0,
columnLink: '',
columnPassUse: 0,
columnPassword: '',
columnCondition: '',
columnText: '',
alreadySave: false,
},
columnCondition: [],
columnText: [],
columnConditionData: [],
columnTextData: [],
record: {},
rules: {
formPassword: [{ required: true, message: '请输入密码', trigger: 'blur' }],
columnPassword: [{ required: true, message: '请输入密码', trigger: 'blur' }],
},
columnOptions: [],
searchOptions: [],
webType: '1',
id: '',
});
const { dataForm, rules, columnCondition, columnText, searchOptions, columnOptions, webType } = toRefs(state);
function init(data) {
changeLoading(true);
state.webType = '1';
state.dataForm = {
id: '',
formLink: '',
formUse: 0,
formPassUse: 0,
formPassword: '',
columnUse: 0,
columnLink: '',
columnPassUse: 0,
columnPassword: '',
columnCondition: '',
columnText: '',
alreadySave: false,
};
state.columnCondition = [];
state.columnText = [];
state.id = data.id;
getOptions(data.id);
nextTick(() => {
clearValidate();
getShortLinkData(data.id);
});
}
function getShortLinkData(id) {
changeLoading(true);
getShortLinkInfo(id).then(res => {
state.dataForm = res.data || {};
state.columnConditionData = state.dataForm.columnCondition ? JSON.parse(state.dataForm.columnCondition) : [];
state.columnTextData = state.dataForm.columnText ? JSON.parse(state.dataForm.columnText) : [];
state.columnCondition = state.columnConditionData.map(o => o.id);
state.columnText = state.columnTextData.map(o => o.id);
changeLoading(false);
});
}
function getOptions(id) {
getInfo(id).then(res => {
state.webType = res.data.webType || '1';
const formData = res.data.formData ? JSON.parse(res.data.formData) : {};
let list: any[] = [];
const loop = (data, parent?) => {
if (!data) return;
if (data.__config__ && data.__config__.children && Array.isArray(data.__config__.children)) {
loop(data.__config__.children, data);
}
if (Array.isArray(data)) data.forEach(d => loop(d, parent));
if (data.__config__ && data.__config__.yunzhupaasKey) {
const visibility = !data.__config__.visibility || (Array.isArray(data.__config__.visibility) && data.__config__.visibility.includes('pc'));
if (data.__config__.layout === 'colFormItem' && data.__vModel__ && visibility) {
const isTableChild = parent && parent.__config__ && parent.__config__.yunzhupaasKey === 'table';
list.push({
id: isTableChild ? parent.__vModel__ + '-' + data.__vModel__ : data.__vModel__,
fullName: isTableChild ? parent.__config__.label + '-' + data.__config__.label : data.__config__.label,
...data,
disabled: false,
});
}
}
};
loop(formData.fields);
list = list.filter(o => {
if (!o.__config__ || !o.__config__.yunzhupaasKey) return true;
const yunzhupaasKey = o.__config__.yunzhupaasKey;
if (validFieldsList.includes(yunzhupaasKey) || (selectFieldsList.includes(yunzhupaasKey) && o.__config__.dataType === 'static')) return true;
return false;
});
const columnOptions = list.filter(o => !noColumnShowList.includes(o.__config__.yunzhupaasKey) || o.isStorage);
const searchOptions = list.filter(o => !noSearchList.includes(o.__config__.yunzhupaasKey));
state.columnOptions = columnOptions.map(o => ({
label: o.fullName,
prop: o.id,
fixed: 'none',
align: 'left',
yunzhupaasKey: o.__config__.yunzhupaasKey,
sortable: false,
width: null,
...o,
}));
state.searchOptions = searchOptions.map(o => ({
label: o.fullName,
prop: o.id,
yunzhupaasKey: o.__config__.yunzhupaasKey,
value: '',
...o,
}));
});
}
function clearValidate() {
formElRef.value?.clearValidate();
}
function handleDownload(type) {
const qrEl = type == 1 ? unref(qrRef) : unref(columnQrRef);
if (!qrEl) return;
qrEl.download('二维码');
}
function handleCopy(text) {
const { isSuccessRef } = useCopyToClipboard(text);
unref(isSuccessRef) && createMessage.success('复制成功');
}
function onConditionChange(_val, data) {
state.columnConditionData = data || [];
}
function onColumnTextChange(_val, data) {
state.columnTextData = data || [];
}
async function handleSubmit() {
try {
const values = await formElRef.value?.validate();
if (!values) return;
changeOkLoading(true);
state.dataForm.columnCondition = JSON.stringify(state.columnConditionData);
state.dataForm.columnText = JSON.stringify(state.columnTextData);
updateShortLink(state.dataForm)
.then(res => {
changeOkLoading(false);
createMessage.success(res.msg);
getShortLinkData(state.id);
})
.catch(() => {
changeOkLoading(false);
});
} catch (_) {}
}
</script>
<style lang="less">
.yunzhupaas-shortLink-modal {
.shortLink-tip {
color: @text-color-label;
}
.ym-custom-qrcode {
font-size: 32px;
line-height: 29px;
height: 29px;
display: inline-block;
cursor: pointer;
color: @text-color-label;
}
.ant-divider {
margin-top: 0 !important;
}
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="数据选择" @ok="handleSubmit" :width="800" class="yunzhupaas-list-modal">
<BasicTable :searchInfo="searchInfo" @register="registerTable" class="yunzhupaas-sub-table"></BasicTable>
</BasicModal>
</template>
<script lang="ts" setup>
import { getDataModelList } from '@/api/systemData/dataModel';
import { reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicTable, useTable } from '@/components/Table';
import { useI18n } from '@/hooks/web/useI18n';
const emit = defineEmits(['register', 'select']);
const { t } = useI18n();
const [registerModal, { closeModal }] = useModalInner(init);
const searchInfo = reactive<Recordable>({
linkId: '0',
});
const [registerTable, { getForm, getSelectRows }] = useTable({
api: getDataModelList,
columns: [
{ title: '表名', dataIndex: 'table' },
{ title: '说明', dataIndex: 'tableName' },
],
useSearchForm: true,
formConfig: {
baseColProps: { span: 8 },
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
],
},
tableSetting: { size: false, setting: false },
isCanResizeParent: true,
resizeHeightOffset: -73,
immediate: false,
rowSelection: { type: 'checkbox' },
});
function init(data) {
searchInfo.linkId = data.dbLinkId ?? '0';
getForm().resetFields();
}
function handleSubmit() {
const selectedData = getSelectRows();
emit('select', selectedData);
closeModal();
}
</script>

View File

@@ -0,0 +1,276 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable">
<template #tableTitle>
<a-dropdown>
<template #overlay>
<a-menu @click="handleAdd">
<a-menu-item :key="item.id" v-for="item in webTypeOptions">{{ item.fullName }}</a-menu-item>
</a-menu>
</template>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add">{{ t('common.addText') }}<DownOutlined /></a-button>
</a-dropdown>
<a-button type="primary" preIcon="icon-ym icon-ym-ai-form" @click="handleAiCreate">AI建表</a-button>
<yunzhupaas-upload-btn url="/api/visualdev/OnlineDev/Actions/Import" accept=".vdd" @on-success="reload"></yunzhupaas-upload-btn>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'isRelease'">
<a-tag :color="record.isRelease == 1 ? 'success' : record.isRelease == 2 ? 'warning' : ''">
{{ record.isRelease == 1 ? '已发布' : record.isRelease == 2 ? '已修改' : '未发布' }}
</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" />
<ViewForm @register="registerViewForm" @reload="reload" />
<CreateMenuModal @register="registerCreateMenuModal" @reload="reload" />
<ShortLinkModal @register="registerShortLink" @reload="reload" />
<DownloadModal @register="registerDownloadModal" />
<PreviewModal @register="registerPreview" />
<AiCreateModal @register="registerAiCreateModal" @reload="handleAiReload" />
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref } from 'vue';
import { getVisualDevList, delVisualDev, copy, exportData, release } from '@/api/onlineDev/visualDev';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useModal } from '@/components/Modal';
import { useBaseStore } from '@/store/modules/base';
import Form from './Form.vue';
import ViewForm from './ViewForm.vue';
import CreateMenuModal from './components/CreateMenuModal.vue';
import ShortLinkModal from './components/ShortLinkModal.vue';
import DownloadModal from './components/DownloadModal.vue';
import AiCreateModal from './components/AiCreateModal.vue';
import { downloadByUrl } from '@/utils/file/download';
import { PreviewModal } from '@/components/CommonModal';
import { DownOutlined } from '@ant-design/icons-vue';
import { useGo } from '@/hooks/web/usePage';
defineOptions({ name: 'onlineDev-webDesign' });
const { createMessage } = useMessage();
const baseStore = useBaseStore();
const { t } = useI18n();
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerViewForm, { openModal: openViewFormModal }] = useModal();
const [registerCreateMenuModal, { openModal: openCreateMenuModal }] = useModal();
const [registerShortLink, { openModal: openShortLinkModal }] = useModal();
const [registerPreview, { openModal: openPreviewModal }] = useModal();
const [registerDownloadModal, { openModal: openDownloadModal }] = useModal();
const [registerAiCreateModal, { openModal: openAiCreateModal }] = useModal();
const webTypeOptions: any[] = [
{ fullName: '表单', id: 2 },
{ fullName: '视图', id: 4 },
];
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName', width: 200 },
{ title: '编码', dataIndex: 'enCode', width: 200 },
{ title: '分类', dataIndex: 'category', width: 150 },
{ title: '类型', dataIndex: 'webType', width: 100, align: 'center', customRender: ({ record }) => (record.webType == 4 ? '视图' : '表单') },
{ title: '创建人', dataIndex: 'creatorUser', width: 120 },
{ title: '创建时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '最后修改时间', dataIndex: 'lastModifyTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '排序', dataIndex: 'sortCode', width: 70, align: 'center' },
{ title: '状态', dataIndex: 'isRelease', width: 80, align: 'center' },
];
const categoryList = ref<any[]>([]);
const searchInfo = reactive({
type: 1,
});
const go = useGo();
const [registerTable, { reload, getForm }] = useTable({
api: getVisualDevList,
columns,
searchInfo,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'category',
label: '分类',
component: 'Select',
componentProps: {
placeholder: '请选择',
showSearch: true,
},
},
{
field: 'webType',
label: '类型',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: webTypeOptions,
},
},
{
field: 'isRelease',
label: '状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '未发布', id: 0 },
{ fullName: '已发布', id: 1 },
{ fullName: '已修改', id: 2 },
],
},
},
],
},
actionColumn: {
width: 220,
title: '操作',
dataIndex: 'action',
},
});
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText'),
onClick: addOrUpdateHandle.bind(null, record.id, record.webType),
},
{
label: '发布',
color: 'error',
modelConfirm: {
title: '发布确认',
content: '发布表单会覆盖当前线上版本, 是否继续?',
onOk: handleRelease.bind(null, record.id),
},
},
{
label: '代码生成',
disabled: !record.isRelease,
onClick: handleDownload.bind(null, record),
},
];
}
function getDropDownActions(record): ActionItem[] {
return [
{
label: '预览表单',
onClick: handlePreview.bind(null, record.id, record.isRelease),
},
{
label: '生成菜单',
ifShow: !!record.isRelease,
onClick: handleCreateMenu.bind(null, record.id),
},
{
label: '复制表单',
modelConfirm: {
content: '您确定要复制该功能表单, 是否继续?',
onOk: handleCopy.bind(null, record.id),
},
},
{
label: '导出表单',
modelConfirm: {
content: '您确定要导出该功能表单, 是否继续?',
onOk: handleExport.bind(null, record.id),
},
},
{
label: '外链设置',
ifShow: !!record.isRelease && record.webType != 4,
onClick: openShortLink.bind(null, record.id),
},
{
label: '数据管理',
ifShow: !!record.isRelease && record.webType == 2,
onClick: openFormData.bind(null, record.id),
},
{
label: '删除表单',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function addOrUpdateHandle(id = '', webType) {
if (webType == 4) return openViewFormModal(true, { id, ...searchInfo, categoryList });
openFormModal(true, { id, ...searchInfo, categoryList });
}
function handleDelete(id) {
delVisualDev(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleDownload(record) {
openDownloadModal(true, { tables: record.tables, id: record.id, hasPackage: record.hasPackage, fullName: record.fullName, webType: record.webType });
}
// 发布
function handleRelease(id) {
release(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
// 生成菜单
function handleCreateMenu(id) {
openCreateMenuModal(true, { id });
}
function handlePreview(id, isRelease) {
openPreviewModal(true, { type: 'webDesign', id, isRelease });
}
function openShortLink(id) {
openShortLinkModal(true, { id });
}
function openFormData(id) {
if (!id) return;
go(`/dataManage?isDataManage=1&id=${id}`);
}
function handleCopy(id) {
copy(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleExport(id) {
exportData(id).then(res => {
downloadByUrl({ url: res.data.url });
});
}
async function getOptions() {
const res = await baseStore.getDictionaryData('businessType');
categoryList.value = res as any[];
getForm().updateSchema({ field: 'category', componentProps: { options: res } });
}
function handleAdd({ key }) {
addOrUpdateHandle('', key);
}
function handleAiCreate() {
openAiCreateModal(true, { categoryList });
}
function handleAiReload(id) {
addOrUpdateHandle(id, 2);
reload();
}
onMounted(() => {
getOptions();
});
</script>