初始代码

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,27 @@
<template>
<BasicModal :width="800" :title="t('sys.errorLog.tableActionDesc')" v-bind="$attrs">
<Description :data="info" @register="register" />
</BasicModal>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue';
import type { ErrorLogInfo } from '#/store';
import { BasicModal } from '@/components/Modal/index';
import { Description, useDescription } from '@/components/Description/index';
import { useI18n } from '@/hooks/web/useI18n';
import { getDescSchema } from './data';
defineProps({
info: {
type: Object as PropType<ErrorLogInfo>,
default: null,
},
});
const { t } = useI18n();
const [register] = useDescription({
column: 2,
schema: getDescSchema()!,
});
</script>

View File

@@ -0,0 +1,67 @@
import { Tag } from 'ant-design-vue';
import { BasicColumn } from '@/components/Table/index';
import { ErrorTypeEnum } from '@/enums/exceptionEnum';
import { useI18n } from '@/hooks/web/useI18n';
const { t } = useI18n();
export function getColumns(): BasicColumn[] {
return [
{
dataIndex: 'type',
title: t('sys.errorLog.tableColumnType'),
width: 80,
customRender: ({ text }) => {
const color =
text === ErrorTypeEnum.VUE
? 'green'
: text === ErrorTypeEnum.RESOURCE
? 'cyan'
: text === ErrorTypeEnum.PROMISE
? 'blue'
: ErrorTypeEnum.AJAX
? 'red'
: 'purple';
return <Tag color={color}>{() => text}</Tag>;
},
},
{
dataIndex: 'url',
title: 'URL',
width: 200,
},
{
dataIndex: 'time',
title: t('sys.errorLog.tableColumnDate'),
width: 160,
},
{
dataIndex: 'file',
title: t('sys.errorLog.tableColumnFile'),
width: 200,
},
{
dataIndex: 'name',
title: 'Name',
width: 200,
},
{
dataIndex: 'message',
title: t('sys.errorLog.tableColumnMsg'),
width: 300,
},
{
dataIndex: 'stack',
title: t('sys.errorLog.tableColumnStackMsg'),
},
];
}
export function getDescSchema(): any {
return getColumns().map(column => {
return {
field: column.dataIndex!,
label: column.title,
};
});
}

View File

@@ -0,0 +1,94 @@
<template>
<div class="p-4">
<template v-for="src in imgList" :key="src">
<img :src="src" v-show="false" alt="" />
</template>
<DetailModal :info="rowInfo" @register="registerModal" />
<BasicTable @register="register" class="error-handle-table">
<template #toolbar>
<a-button @click="fireVueError" type="primary">
{{ t('sys.errorLog.fireVueError') }}
</a-button>
<a-button @click="fireResourceError" type="primary">
{{ t('sys.errorLog.fireResourceError') }}
</a-button>
<a-button @click="fireAjaxError" type="primary">
{{ t('sys.errorLog.fireAjaxError') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
label: t('sys.errorLog.tableActionDesc'),
onClick: handleDetail.bind(null, record),
},
]" />
</template>
</template>
</BasicTable>
</div>
</template>
<script lang="ts" setup>
import type { ErrorLogInfo } from '#/store';
import { watch, ref, nextTick } from 'vue';
import DetailModal from './DetailModal.vue';
import { BasicTable, useTable, TableAction } from '@/components/Table/index';
import { useModal } from '@/components/Modal';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useErrorLogStore } from '@/store/modules/errorLog';
// import { fireErrorApi } from '@/api/demo/error';
import { getColumns } from './data';
import { cloneDeep } from 'lodash-es';
const rowInfo = ref<ErrorLogInfo>();
const imgList = ref<string[]>([]);
const { t } = useI18n();
const errorLogStore = useErrorLogStore();
const [register, { setTableData }] = useTable({
title: t('sys.errorLog.tableTitle'),
columns: getColumns(),
actionColumn: {
width: 80,
title: 'Action',
dataIndex: 'action',
// slots: { customRender: 'action' },
},
});
const [registerModal, { openModal }] = useModal();
watch(
() => errorLogStore.getErrorLogInfoList,
list => {
nextTick(() => {
setTableData(cloneDeep(list));
});
},
{ immediate: true },
);
const { createMessage } = useMessage();
if (import.meta.env.DEV) {
createMessage.info(t('sys.errorLog.enableMessage'));
}
// 查看详情
function handleDetail(row: ErrorLogInfo) {
rowInfo.value = row;
openModal(true);
}
function fireVueError() {
throw new Error('fire vue error!');
}
function fireResourceError() {
imgList.value.push(`${new Date().getTime()}.png`);
}
async function fireAjaxError() {
// await fireErrorApi();
}
</script>

View File

@@ -0,0 +1,93 @@
<template>
<div class="yunzhupaas-content-wrapper http404-container bg-white">
<div class="http404">
<img src="../../../assets/images/404.png" alt="404" class="pic-404" />
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ t('views.http404.tips') }}</div>
<div class="bullshit__info">{{ t('views.http404.subTips') }}</div>
<a-button type="primary" size="large" @click="$router.push('/home')">{{ t('views.http404.goBackBtn') }}</a-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n';
const { t } = useI18n();
</script>
<style lang="less" scoped>
.http404-container {
display: flex;
justify-content: center;
align-items: center;
}
.http404 {
.pic-404 {
position: relative;
float: left;
width: 500px;
overflow: hidden;
margin-right: 100px;
}
.bullshit {
position: relative;
float: left;
width: 350px;
padding: 90px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
color: @primary-color;
}
&__headline {
font-size: 20px;
line-height: 24px;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 40px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
float: left;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

View File

@@ -0,0 +1 @@
export { default as Exception } from './Exception.vue';

View File

@@ -0,0 +1,46 @@
<template>
<div class="home-default-v">
<GrowCard :loading="loading" class="enter-y" />
<SiteAnalysis class="!my-10px enter-y" :loading="loading" />
<div class="md:flex enter-y">
<VisitRadar class="md:w-1/3 w-full" :loading="loading" />
<VisitSource class="md:w-1/3 !md:mx-10px !md:my-0 !my-10px w-full" :loading="loading" />
<SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
</div>
<p class="copyright enter-y">{{ copyright }}</p>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import GrowCard from './components/GrowCard.vue';
import SiteAnalysis from './components/SiteAnalysis.vue';
import VisitSource from './components/VisitSource.vue';
import VisitRadar from './components/VisitRadar.vue';
import SalesProductPie from './components/SalesProductPie.vue';
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();
const { copyright } = appStore.getSysConfigInfo;
const loading = ref(true);
setTimeout(() => {
loading.value = false;
}, 500);
</script>
<style lang="less">
.home-default-v {
.ant-card {
border-radius: 10px;
overflow: hidden;
}
.ant-card-contain-tabs {
z-index: 0 !important;
}
.copyright {
font-size: 14px;
text-align: center;
padding: 20px 0 10px;
color: #999;
}
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" width="340px" class="full-drawer portal-toggle-drawer" title="切换门户">
<div class="tool">
<a-input-search :placeholder="t('common.drawerSearchText')" allowClear v-model:value="keyword" />
</div>
<div class="main">
<div v-if="getPortalList.length">
<div class="item" v-for="(item, i) in getPortalList" :key="i">
<p class="item-title">{{ item.fullName }}</p>
<div class="item-list">
<div class="item-list-item" v-for="(child, ii) in item.children" :key="ii" @click="selectItem(child.id)" :class="{ active: activeId === child.id }">
<p class="item-list-item-name com-hover" :title="child.fullName">{{ child.fullName }}</p>
<CheckCircleFilled class="icon-right" />
</div>
</div>
</div>
</div>
<yunzhupaas-empty v-else />
</div>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
import { useMessage } from '@/hooks/web/useMessage';
import { getPortalSelector, setDefaultPortal } from '@/api/onlineDev/portal';
import { reactive, toRefs, computed, unref } from 'vue';
import { CheckCircleFilled } from '@ant-design/icons-vue';
import { useUserStore } from '@/store/modules/user';
import { useI18n } from '@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es';
interface State {
list: any[];
activeId: string;
keyword: string;
}
const state = reactive<State>({
list: [],
activeId: '',
keyword: '',
});
const { activeId, keyword } = toRefs(state);
const userStore = useUserStore();
const emit = defineEmits(['register', 'refresh']);
const { createMessage } = useMessage();
const { t } = useI18n();
const [registerDrawer, { changeLoading, closeDrawer }] = useDrawerInner(init);
const getUserInfo = computed(() => userStore.getUserInfo || {});
const getPortalList = computed(() => {
const newList: any = [];
let list = cloneDeep(state.list);
for (let i = 0; i < list.length; i++) {
const item = list[i];
item.children = item.children?.length ? item.children.filter(o => o.fullName.indexOf(state.keyword) !== -1) : [];
newList.push(item);
}
return newList.filter(o => o.children && o.children.length);
});
function init(data) {
state.activeId = data.id || '';
state.keyword = '';
initData();
}
function initData() {
changeLoading(true);
getPortalSelector(1, unref(getUserInfo).systemId)
.then(res => {
state.list = res?.data?.list || [];
changeLoading(false);
})
.catch(() => {
changeLoading(false);
});
}
function selectItem(id) {
if (state.activeId == id) return;
changeLoading(true);
setDefaultPortal(id)
.then(res => {
state.activeId = id;
emit('refresh', id);
changeLoading(false);
createMessage.success(res.msg);
closeDrawer();
userStore.setUserInfo({ portalId: id });
})
.catch(() => {
changeLoading(false);
});
}
</script>

View File

@@ -0,0 +1,86 @@
<template>
<div class="md:flex home-grow-card">
<template v-for="(item, index) in growCardList" :key="item.title">
<Card
size="small"
:loading="loading"
class="home-grow-card-item md:w-1/4 w-full !md:mt-0"
:class="{ '!md:mr-10px': index + 1 < 4, '!mt-10px': index > 0 }"
:style="{ background: !isDark ? item.bg : '' }">
<div class="home-grow-card-item-main-img">
<img :src="item.mainImg" />
</div>
<img :src="item.icon" class="home-grow-card-item-icon" />
<div class="home-grow-card-item-content">
<div class="flex justify-start items-center mb-6px">
<span>{{ item.title }}</span>
<span class="item-tag" :style="{ background: item.tagBg, color: item.color }">{{ item.action }}</span>
</div>
<CountTo :startVal="1" :endVal="item.value" class="text-24px font-bold leading-30px" />
<div class="flex justify-between mt-53px">
<span>{{ item.title }}</span>
<CountTo :startVal="1" :endVal="item.total" />
</div>
</div>
</Card>
</template>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { CountTo } from '@/components/CountTo/index';
import { Card } from 'ant-design-vue';
import { growCardList } from '../data';
import { useRootSetting } from '@/hooks/setting/useRootSetting';
import { ThemeEnum } from '@/enums/appEnum';
defineProps({
loading: { type: Boolean },
});
const { getDarkMode } = useRootSetting();
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
</script>
<style lang="less" scoped>
.home-grow-card {
.home-grow-card-item {
border-radius: 10px !important;
overflow: hidden;
height: 180px;
:deep(.ant-card-body) {
height: 100%;
padding: 20px 0 0 20px !important;
position: relative;
display: flex;
}
.home-grow-card-item-icon {
width: 50px;
height: 50px;
flex-shrink: 0;
margin-right: 13px;
}
.home-grow-card-item-main-img {
position: absolute;
bottom: 0;
right: 0;
width: 163px;
height: 153px;
z-index: 0;
}
.home-grow-card-item-content {
position: relative;
z-index: 1;
.item-tag {
display: inline-block;
width: 32px;
height: 20px;
border-radius: 5px;
font-size: 12px;
text-align: center;
line-height: 20px;
margin-left: 8px;
}
}
}
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<Card title="成交占比" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
trigger: 'item',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '80%',
center: ['50%', '50%'],
color: ['#fd8f79', '#f85f8a', '#e2b3ee', '#fed361'],
data: [
{ value: 500, name: '电子产品' },
{ value: 310, name: '服装' },
{ value: 274, name: '化妆品' },
{ value: 400, name: '家居' },
].sort(function (a, b) {
return a.value - b.value;
}),
roseType: 'radius',
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay: function () {
return Math.random() * 400;
},
},
],
});
},
{ immediate: true },
);
</script>

View File

@@ -0,0 +1,33 @@
<template>
<Card :tab-list="tabListTitle" v-bind="$attrs" :active-tab-key="activeKey" @tab-change="onTabChange">
<p v-if="activeKey === 'tab1'">
<VisitAnalysis />
</p>
<p v-if="activeKey === 'tab2'">
<VisitAnalysisBar />
</p>
</Card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Card } from 'ant-design-vue';
import VisitAnalysis from './VisitAnalysis.vue';
import VisitAnalysisBar from './VisitAnalysisBar.vue';
const activeKey = ref('tab1');
const tabListTitle = [
{
key: 'tab1',
tab: '流量趋势',
},
{
key: 'tab2',
tab: '访问量',
},
];
function onTabChange(key) {
activeKey.value = key;
}
</script>

View File

@@ -0,0 +1,93 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script lang="ts">
import { basicProps } from './props';
</script>
<script lang="ts" setup>
import { onMounted, ref, Ref } from 'vue';
import { useECharts } from '@/hooks/web/useECharts';
import * as echarts from 'echarts';
defineProps({
...basicProps,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
onMounted(() => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: '#019680',
},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [...new Array(18)].map((_item, index) => `${index + 6}:00`),
splitLine: {
show: true,
lineStyle: {
width: 1,
type: 'solid',
color: 'rgba(226,226,226,0.1)',
},
},
axisTick: {
show: false,
},
axisLine: {
show: false,
},
},
yAxis: [
{
type: 'value',
max: 80000,
splitNumber: 4,
axisTick: {
show: false,
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(255,255,255,0.1)'],
},
},
},
],
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
series: [
{
smooth: true,
data: [111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222, 11111, 4000, 2000, 500, 333, 222, 111],
type: 'line',
areaStyle: {},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#65AAFA' },
{ offset: 1, color: '#fff' },
]),
},
},
{
smooth: true,
data: [33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390, 198, 60, 30, 22, 11],
type: 'line',
areaStyle: {},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(33,173,130,0.6)' },
{ offset: 1, color: '#fff' },
]),
},
},
],
});
});
</script>

View File

@@ -0,0 +1,48 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script lang="ts">
import { basicProps } from './props';
</script>
<script lang="ts" setup>
import { onMounted, ref, Ref } from 'vue';
import { useECharts } from '@/hooks/web/useECharts';
defineProps({
...basicProps,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
onMounted(() => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: '#019680',
},
},
},
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
xAxis: {
type: 'category',
data: [...new Array(12)].map((_item, index) => `${index + 1}`),
},
yAxis: {
type: 'value',
max: 8000,
splitNumber: 4,
},
series: [
{
data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
type: 'bar',
barMaxWidth: 80,
color: 'rgba(24,144,255,0.8)',
},
],
});
});
</script>

View File

@@ -0,0 +1,94 @@
<template>
<Card title="转化率" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
legend: {
bottom: 0,
data: ['访问', '购买'],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: '电脑',
},
{
name: '充电器',
},
{
name: '耳机',
},
{
name: '手机',
},
{
name: 'Ipad',
},
{
name: '耳机',
},
],
},
series: [
{
type: 'radar',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: '访问',
itemStyle: {
color: '#786fe1',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: '购买',
itemStyle: {
color: '#1890ff',
},
},
],
},
],
});
},
{ immediate: true },
);
</script>

View File

@@ -0,0 +1,81 @@
<template>
<Card title="访问来源" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
trigger: 'item',
},
legend: {
bottom: '1%',
left: 'center',
},
series: [
{
color: ['#1890ff', '#36c1e2', '#e2b3ee', '#786fe1'],
name: '访问来源',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '12',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
],
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay: function () {
return Math.random() * 100;
},
},
],
});
},
{ immediate: true },
);
</script>

View File

@@ -0,0 +1,16 @@
import { PropType } from 'vue';
export interface BasicProps {
width: string;
height: string;
}
export const basicProps = {
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '280px',
},
};

View File

@@ -0,0 +1,67 @@
import growIcon1 from '@/assets/images/home/grow-icon1.png';
import growIcon2 from '@/assets/images/home/grow-icon2.png';
import growIcon3 from '@/assets/images/home/grow-icon3.png';
import growIcon4 from '@/assets/images/home/grow-icon4.png';
import growImg1 from '@/assets/images/home/grow-img1.png';
import growImg2 from '@/assets/images/home/grow-img2.png';
import growImg3 from '@/assets/images/home/grow-img3.png';
import growImg4 from '@/assets/images/home/grow-img4.png';
export interface GrowCardItem {
icon: string;
mainImg: string;
title: string;
value: number;
total: number;
color: string;
bg: string;
tagBg: string;
action: string;
}
export const growCardList: GrowCardItem[] = [
{
title: '访问数',
icon: growIcon1,
mainImg: growImg1,
value: 2000,
total: 120000,
color: '#21AD82',
tagBg: 'rgba(33,173,130,0.1)',
bg: 'linear-gradient(58deg, #F7FFFE 0%, #E7FFFE 67%, #E2FCF8 100%)',
action: '月',
},
{
title: '成交额',
icon: growIcon2,
mainImg: growImg2,
value: 20000,
total: 500000,
color: '#CD7326',
tagBg: 'rgba(205,115,38,0.1)',
bg: 'linear-gradient(58deg, #FFFCF4 0%, #FFF9F4 67%, #FFE2D0 100%)',
action: '月',
},
{
title: '下载数',
icon: growIcon3,
mainImg: growImg3,
value: 8000,
total: 120000,
color: '#294DE5',
tagBg: 'rgba(41,70,229,0.1)',
bg: 'linear-gradient(58deg, #F7FAFF 0%, #EFF5FF 67%, #D0E0FD 100%)',
action: '周',
},
{
title: '成交数',
icon: growIcon4,
mainImg: growImg4,
value: 5000,
total: 50000,
color: '#2F92E6',
tagBg: 'rgba(41,128,229,0.1)',
bg: 'linear-gradient(58deg, #F5F8FF 0%, #EFF6FF 67%, #D0E6FD 100%)',
action: '年',
},
];

View File

@@ -0,0 +1,155 @@
import { reactive, defineAsyncComponent, markRaw } from 'vue';
import { getAuthPortal, UpdateCustomPortal } from '@/api/onlineDev/portal';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { importViewsFile } from '@/utils';
import { getParamList } from '@/utils/yunzhupaas';
interface State {
portalId: string;
layout: any[];
type: number;
linkType: number;
currentView: string;
url: string;
ajaxing: boolean;
loading: boolean;
noData: boolean;
refreshData: any;
timerList: any[];
formData: any;
enabledLock: number;
systemId: string;
}
const state = reactive<State>({
portalId: '',
layout: [],
type: 0,
linkType: 0,
currentView: '',
url: '',
ajaxing: true,
loading: false,
noData: false,
refreshData: {},
timerList: [],
formData: {},
enabledLock: 1,
systemId: '',
});
export function usePortal() {
function initData() {
state.loading = true;
state.layout = [];
state.noData = false;
if (!state.portalId) {
state.loading = false;
state.ajaxing = false;
state.noData = true;
return;
}
getAuthPortal(state.portalId, { platform: 'Web', systemId: state.systemId })
.then(res => {
if (res.data) {
state.type = res.data.type || 0;
state.linkType = res.data.linkType || 0;
state.url = res.data.customUrl || '';
state.enabledLock = res.data.enabledLock || 0;
if (res.data.type === 1) {
if (res.data.customUrl && res.data.customUrl !== 1) {
const formUrl = `${res.data.customUrl}`;
state.currentView = markRaw(defineAsyncComponent(() => importViewsFile(formUrl)));
}
} else {
if (res.data.formData) {
state.formData = JSON.parse(res.data.formData);
state.layout = filterList(JSON.parse(JSON.stringify(state.formData.layout)) || []);
state.refreshData = state.formData.refresh || {};
}
}
}
state.ajaxing = false;
state.loading = false;
setTimeout(() => {
initAutoRefresh();
}, 500);
})
.catch(() => {
state.loading = false;
state.ajaxing = false;
state.noData = true;
});
}
function filterList(layout) {
const loop = list => {
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (!(Array.isArray(item.visibility) && item.visibility.includes('pc')) && item.yunzhupaasKey) {
list.splice(i, 1);
i--;
}
if (item.children && item.children.length) loop(item.children);
}
};
loop(layout);
return layout;
}
function initAutoRefresh() {
if (!state.layout.length) return;
state.timerList = [];
const loop = (list, type = 1) => {
list.forEach(ele => {
if ((ele.refresh && ele.refresh.autoRefresh && ele.refresh.autoRefreshTime) || type == 2) {
var timer = setInterval(
() => {
ele.renderKey = +new Date();
autoRefresh(ele);
},
type == 2 ? state.refreshData.autoRefreshTime * 1000 * 60 : ele.refresh.autoRefreshTime * 1000 * 60,
);
state.timerList.push(timer);
}
if (ele.children && ele.children.length) loop(ele.children, type);
});
};
if (state.refreshData.autoRefresh) {
loop(state.layout, 2);
} else {
loop(state.layout);
}
}
function autoRefresh(item) {
const chartList = ['barChart', 'lineChart', 'pieChart', 'radarChart', 'mapChart'];
if (item.dataType === 'dynamic' && chartList.includes(item.yunzhupaasKey)) {
item.option.defaultValue = [];
if (!item.propsApi) return;
const query = { paramList: getParamList(item.templateJson) };
getDataInterfaceRes(item.propsApi, query).then(res => {
const realData = res.data;
item.option.defaultValue = Array.isArray(realData) ? realData : [];
});
}
}
function clearAutoRefresh() {
if (state.timerList.length) {
state.timerList.forEach(ele => {
if (ele) clearInterval(ele);
});
}
}
function layoutUpdatedEvent() {
state.formData.layout = state.layout;
const query = { formData: JSON.stringify(state.formData), systemId: state.systemId };
UpdateCustomPortal(state.portalId, query);
}
return {
state: state,
initData,
filterList,
initAutoRefresh,
autoRefresh,
clearAutoRefresh,
layoutUpdatedEvent,
};
}

View File

@@ -0,0 +1,102 @@
<template>
<div class="dashboard-container" v-loading="loading">
<template v-if="!noData">
<template v-if="!ajaxing">
<template v-if="portalId">
<PortalLayout :layout="layout" :enabledLock="enabledLock" v-if="type === 0" @layoutUpdatedEvent="layoutUpdatedEvent" />
<div class="custom-page" v-if="type === 1">
<component :is="currentView" v-if="linkType === 0" />
<embed :src="url" width="100%" height="100%" type="text/html" v-if="linkType === 1" />
</div>
</template>
<div class="portal-layout-nodata" v-else>
<yunzhupaas-empty :image="emptyImage" />
</div>
</template>
<Setting @register="registerSettingDrawer" @refresh="refresh" />
<a-button type="primary" preIcon="icon-ym icon-ym-left" class="setting-btn" size="large" @click="openSettingDrawer(true, { id: portalId })"></a-button>
</template>
<template v-else>
<ScrollContainer class="dashboard-container">
<Default />
</ScrollContainer>
</template>
</div>
</template>
<script lang="ts" setup>
import { toRefs, computed, onMounted, onUnmounted, unref } from 'vue';
import PortalLayout from '@/components/VisualPortal/Portal/Layout/index.vue';
import { ScrollContainer } from '@/components/Container';
import { useUserStore } from '@/store/modules/user';
import { useDrawer } from '@/components/Drawer';
import Default from './Default.vue';
import Setting from './Setting.vue';
import { usePortal } from '@/views/basic/home/hooks/usePortal';
import emptyImage from '@/assets/images/dashboard-nodata.png';
const { state, initData, clearAutoRefresh, layoutUpdatedEvent } = usePortal();
const { portalId, layout, type, linkType, currentView, url, ajaxing, loading, noData, enabledLock } = toRefs(state);
const userStore = useUserStore();
const [registerSettingDrawer, { openDrawer: openSettingDrawer }] = useDrawer();
const getUserInfo = computed(() => userStore.getUserInfo || {});
function init() {
state.systemId = unref(getUserInfo)?.systemId;
state.portalId = unref(getUserInfo)?.portalId as string;
initData();
}
function refresh(id) {
if (!id) return;
state.portalId = id;
clearAutoRefresh();
initData();
}
onMounted(() => init());
onUnmounted(() => clearAutoRefresh());
</script>
<style lang="less" scoped>
.dashboard-container {
width: 100%;
height: 100%;
position: relative;
border-radius: 8px;
.custom-page {
width: 100%;
height: 100%;
}
:deep(.layout-area) {
width: 100%;
border-radius: 4px;
overflow: hidden;
}
.setting-btn {
position: fixed;
top: 300px;
right: 0px;
height: 40px;
width: 40px;
text-align: center;
padding: 0;
border-radius: 20px 0 0 20px;
z-index: 100;
:deep(i) {
font-size: 20px;
font-weight: 580;
}
}
:deep(.vue-grid-layout) {
margin: -10px;
}
:deep(.scrollbar__view) {
overflow: hidden;
}
:deep(.ant-card) {
border: unset;
}
}
</style>
<style lang="less">
@import '@/components/VisualPortal/style/index.less';
</style>

View File

@@ -0,0 +1,9 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'FrameBlank',
});
</script>

View File

@@ -0,0 +1,95 @@
<template>
<div :class="prefixCls" :style="getWrapStyle">
<Spin :spinning="loading" size="large" :style="getWrapStyle">
<!-- <iframe :src="frameSrc" :class="`${prefixCls}__main`" ref="frameRef" @load="hideLoading"></iframe> -->
</Spin>
</div>
</template>
<script lang="ts" setup>
import type { CSSProperties } from 'vue';
import { ref, unref, computed } from 'vue';
import { Spin } from 'ant-design-vue';
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
import { propTypes } from '@/utils/propTypes';
import { useDesign } from '@/hooks/web/useDesign';
import { useLayoutHeight } from '@/layouts/default/content/useContentViewHeight';
defineProps({
frameSrc: propTypes.string.def(''),
});
const loading = ref(true);
const topRef = ref(50);
const pagePaddingRef = ref(20);
const heightRef = ref(window.innerHeight);
const frameRef = ref<HTMLFrameElement>();
const { headerHeightRef } = useLayoutHeight();
const { prefixCls } = useDesign('iframe-page');
useWindowSizeFn(calcHeight, 150, { immediate: true });
// 自动登录账号
const AUTO_LOGIN = {
username: 'admin',
password: 'Szlc@2025'
};
const getWrapStyle = computed((): CSSProperties => {
return {
height: `${unref(heightRef)}px`,
};
});
function calcHeight() {
const iframe = unref(frameRef);
if (!iframe) {
return;
}
const top = headerHeightRef.value;
const pagePadding = pagePaddingRef.value;
const remainHeight = top + pagePadding;
topRef.value = remainHeight;
heightRef.value = window.innerHeight - remainHeight;
const clientHeight = document.documentElement.clientHeight - remainHeight;
iframe.style.height = `${clientHeight}px`;
}
function hideLoading() {
loading.value = false;
calcHeight();
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-iframe-page';
.@{prefix-cls} {
border-radius: 8px;
overflow: hidden;
.ant-spin-nested-loading {
position: relative;
height: 100%;
.ant-spin-container {
width: 100%;
height: 100%;
padding: 10px;
}
}
&__mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
&__main {
width: 100%;
height: 100%;
overflow: hidden;
background-color: @component-background;
border: 0;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div :class="prefixCls" :style="getWrapStyle">
<Spin :spinning="loading" size="large" :style="getWrapStyle">
<iframe
:src="finalFrameSrc"
:class="`${prefixCls}__main`"
ref="frameRef"
@load="handleIframeLoad"
allow="popups"
sandbox="allow-scripts allow-same-origin allow-popups allow-top-navigation"
></iframe>
</Spin>
</div>
</template>
<script lang="ts" setup>
import type { CSSProperties } from 'vue';
import { ref, unref, computed, onMounted } from 'vue';
import { Spin } from 'ant-design-vue';
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
import { propTypes } from '@/utils/propTypes';
import { useDesign } from '@/hooks/web/useDesign';
import { useLayoutHeight } from '@/layouts/default/content/useContentViewHeight';
const props = defineProps({
frameSrc: propTypes.string.def(''),
});
const loading = ref(true);
const pagePaddingRef = ref(20);
const heightRef = ref(window.innerHeight);
const frameRef = ref<HTMLIFrameElement | null>(null);
const { headerHeightRef } = useLayoutHeight();
const { prefixCls } = useDesign('iframe-page');
useWindowSizeFn(calcHeight, 150, { immediate: true });
// ===================== 最终 iframe 地址(只加载一次,修复重复请求) =====================
const finalFrameSrc = computed(() => {
const src = props.frameSrc;
if (!src) return '';
const username = localStorage.getItem('loginAccount') || '';
const password = 'yunzhupass6.0';
// 拼接自动登录参数 + 时间戳防缓存(只计算一次)
return `${src}?autoLogin=1&username=${username}&password=${password}&t=${new Date().getTime()}`;
});
// ====================================================================================
const getWrapStyle = computed((): CSSProperties => {
return { height: `${unref(heightRef)}px` };
});
function calcHeight() {
const iframe = unref(frameRef);
if (!iframe) return;
const top = headerHeightRef.value;
const pagePadding = pagePaddingRef.value;
const remainHeight = top + pagePadding;
heightRef.value = window.innerHeight - remainHeight;
const clientHeight = document.documentElement.clientHeight - remainHeight;
iframe.style.height = `${clientHeight}px`;
}
function handleIframeLoad() {
loading.value = false;
calcHeight();
}
onMounted(() => {
calcHeight();
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-iframe-page';
.@{prefix-cls} {
border-radius: 8px;
overflow: hidden;
.ant-spin-nested-loading {
position: relative;
height: 100%;
.ant-spin-container {
width: 100%;
height: 100%;
padding: 10px;
}
}
&__main {
width: 100%;
height: 100%;
overflow: hidden;
background-color: @component-background;
border: 0;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<div :class="prefixCls" class="fixed inset-0 flex h-screen w-screen bg-black items-center justify-center">
<div
:class="`${prefixCls}__unlock`"
class="absolute top-0 left-1/2 flex pt-5 h-16 items-center justify-center sm:text-md xl:text-xl text-white flex-col cursor-pointer transform translate-x-1/2"
@click="handleShowForm(false)"
v-show="showDate">
<LockOutlined />
<span>{{ t('sys.lock.unlock') }}</span>
</div>
<div class="flex w-screen h-screen justify-center items-center">
<div :class="`${prefixCls}__hour`" class="relative mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5">
<span>{{ hour }}</span>
<span class="meridiem absolute left-5 top-5 text-md xl:text-xl" v-show="showDate">
{{ meridiem }}
</span>
</div>
<div :class="`${prefixCls}__minute w-2/5 h-2/5 md:h-4/5 `">
<span> {{ minute }}</span>
</div>
</div>
<transition name="fade-slide">
<div :class="`${prefixCls}-entry`" v-show="!showDate">
<div :class="`${prefixCls}-entry-content`">
<div :class="`${prefixCls}-entry__header enter-x`">
<Avatar :class="`${prefixCls}-entry__header-img`" :src="apiUrl + userInfo.headIcon" :size="140" />
<p :class="`${prefixCls}-entry__header-name`"> {{ userInfo.userName }}/{{ userInfo.userAccount }} </p>
</div>
<InputPassword :placeholder="t('sys.lock.placeholder')" class="enter-x" v-model:value="password" @keyup.enter="unLock()" />
<div :class="`${prefixCls}-entry__footer enter-x`">
<a-button type="link" size="small" class="mt-2 mr-2 enter-x" :disabled="loading" @click="handleShowForm(true)">
{{ t('common.back') }}
</a-button>
<a-button type="link" size="small" class="mt-2 mr-2 enter-x" :disabled="loading" @click="goLogin">
{{ t('sys.lock.backToLogin') }}
</a-button>
<a-button class="mt-2" type="link" size="small" @click="unLock()" :loading="loading">
{{ t('sys.lock.entry') }}
</a-button>
</div>
</div>
</div>
</transition>
<div class="absolute bottom-5 w-full text-gray-300 xl:text-xl 2xl:text-3xl text-center enter-y">
<div class="text-5xl mb-4 enter-x" v-show="!showDate">
{{ hour }}:{{ minute }} <span class="text-3xl">{{ meridiem }}</span>
</div>
<div class="text-2xl">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { Input, Avatar } from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user';
import { useLockStore } from '@/store/modules/lock';
import { useI18n } from '@/hooks/web/useI18n';
import { useNow } from './useNow';
import { useDesign } from '@/hooks/web/useDesign';
import { useMessage } from '@/hooks/web/useMessage';
import { LockOutlined } from '@ant-design/icons-vue';
import { useGlobSetting } from '@/hooks/setting';
import { encryptByMd5 } from '@/utils/cipher';
const InputPassword = Input.Password;
const password = ref('');
const loading = ref(false);
const errMsg = ref(false);
const showDate = ref(true);
const { prefixCls } = useDesign('lock-page');
const { createMessage } = useMessage();
const lockStore = useLockStore();
const userStore = useUserStore();
const globSetting = useGlobSetting();
const apiUrl = globSetting.apiUrl;
const { hour, month, minute, meridiem, year, day, week } = useNow(true);
const { t } = useI18n();
const userInfo = computed(() => {
return userStore.getUserInfo || {};
});
/**
* @description: unLock
*/
async function unLock() {
if (!password.value) {
createMessage.error(t('sys.lock.placeholder'));
return;
}
let pwd = password.value;
try {
loading.value = true;
const res = await lockStore.unLock(encryptByMd5(pwd));
errMsg.value = !res;
} finally {
loading.value = false;
}
}
function goLogin() {
userStore.logout(true);
lockStore.resetLockInfo();
}
function handleShowForm(show = false) {
showDate.value = show;
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-lock-page';
.@{prefix-cls} {
z-index: @lock-page-z-index;
&__unlock {
transform: translate(-50%, 0);
}
&__hour,
&__minute {
display: flex;
font-weight: 700;
color: #bababa;
background-color: #141313;
border-radius: 30px;
justify-content: center;
align-items: center;
@media screen and (max-width: @screen-md) {
span:not(.meridiem) {
font-size: 160px;
}
}
@media screen and (min-width: @screen-md) {
span:not(.meridiem) {
font-size: 160px;
}
}
@media screen and (max-width: @screen-sm) {
span:not(.meridiem) {
font-size: 90px;
}
}
@media screen and (min-width: @screen-lg) {
span:not(.meridiem) {
font-size: 220px;
}
}
@media screen and (min-width: @screen-xl) {
span:not(.meridiem) {
font-size: 260px;
}
}
@media screen and (min-width: @screen-2xl) {
span:not(.meridiem) {
font-size: 320px;
}
}
}
&-entry {
position: absolute;
top: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
background-color: rgb(0 0 0 / 50%);
backdrop-filter: blur(8px);
justify-content: center;
align-items: center;
&-content {
width: 260px;
transform: translateY(-100px);
}
&__header {
text-align: center;
&-img {
margin: 0 auto;
}
&-name {
margin: 10px 0;
font-weight: 500;
color: #bababa;
}
}
&__err-msg {
display: inline-block;
margin-top: 10px;
color: @error-color;
}
&__footer {
display: flex;
justify-content: space-between;
}
}
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
<transition name="fade-bottom" mode="out-in">
<LockPage v-if="getIsLock" />
</transition>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import LockPage from './LockPage.vue';
import { useLockStore } from '@/store/modules/lock';
const lockStore = useLockStore();
const getIsLock = computed(() => lockStore?.getLockInfo?.isLock ?? false);
</script>

View File

@@ -0,0 +1,60 @@
import { dateUtil } from '@/utils/dateUtil';
import { reactive, toRefs } from 'vue';
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
export function useNow(immediate = true) {
let timer: IntervalHandle;
const state = reactive({
year: 0,
month: 0,
week: '',
day: 0,
hour: '',
minute: '',
second: 0,
meridiem: '',
});
const update = () => {
const now = dateUtil();
const h = now.format('HH');
const m = now.format('mm');
const s = now.get('s');
state.year = now.get('y');
state.month = now.get('M') + 1;
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()];
state.day = now.get('date');
state.hour = h;
state.minute = m;
state.second = s;
state.meridiem = now.format('A');
};
function start() {
update();
clearInterval(timer);
timer = setInterval(() => update(), 1000);
}
function stop() {
clearInterval(timer);
}
tryOnMounted(() => {
immediate && start();
});
tryOnUnmounted(() => {
stop();
});
return {
...toRefs(state),
start,
stop,
};
}

View File

@@ -0,0 +1,48 @@
<template>
<div :class="prefixCls">
<div class="login-version" v-if="getSysConfig && getSysConfig.sysVersion">
<p class="login-version-text">{{ getSysConfig.sysVersion }}</p>
</div>
<div class="flex items-center absolute right-60px top-80px">
<AppDarkModeToggle class="enter-x" v-if="!sessionTimeout" />
</div>
<div class="login-header">
<a class="login-company-logo" target="_blank" href="http://www.yunzhupaas.cn/">
<img class="login-company-logo-img -enter-x" src="@/assets/images/login-company-logo.png" alt="" />
</a>
</div>
<div class="login-content">
<div class="login-left hidden xl:block">
<LoginFormTitle class="-enter-x" />
<img class="login-banner -enter-x" src="@/assets/images/login-banner.png" alt="" />
</div>
<div :class="`${prefixCls}-form`" class="enter-x h-630px xl:h-full">
<LoginFormTitle class="-enter-x xl:hidden" />
<LoginForm />
</div>
</div>
<div class="copyright">{{ getSysConfig.copyright }}</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { AppDarkModeToggle } from '@/components/Application';
import LoginFormTitle from './LoginFormTitle.vue';
import LoginForm from './LoginForm.vue';
import { useDesign } from '@/hooks/web/useDesign';
import { useAppStore } from '@/store/modules/app';
defineProps({
sessionTimeout: {
type: Boolean,
},
});
const { prefixCls } = useDesign('login-container');
const appStore = useAppStore();
const getSysConfig = computed(() => appStore.getSysConfigInfo);
</script>
<style lang="less">
@import url('./index.less');
</style>

View File

@@ -0,0 +1,358 @@
<template>
<div class="login-cap">{{ t('sys.login.welcome') }}</div>
<template v-if="!isSso && !ssoLoading">
<div class="login-sub-title" v-if="activeTab == 1">
{{ t('sys.login.subTitle2') }}<span @click="activeTab = 2">{{ t('sys.login.qrSignInFormTitle') }}</span>
</div>
<div class="login-sub-title" v-if="activeTab == 2">
{{ t('sys.login.subTitle3') }}<span @click="activeTab = 1">{{ t('sys.login.signInFormTitle') }}</span>
</div>
</template>
<div v-show="!isSso && !ssoLoading">
<Form class="enter-x" :model="formData" :rules="getFormRules" ref="formRef" v-show="activeTab == 1">
<FormItem name="account" class="enter-x">
<Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.username')" class="fix-auto-fill" @blur="onAccountChange" />
</FormItem>
<FormItem name="password" class="enter-x">
<InputPassword size="large" v-model:value="formData.password" :placeholder="t('sys.login.password')" />
</FormItem>
<FormItem name="code" class="enter-x" v-if="state.needCode">
<a-row type="flex" justify="space-between">
<a-col class="sms-input">
<a-input v-model:value="formData.code" :placeholder="t('sys.login.codeTip')" name="code" size="large" />
</a-col>
<a-col class="sms-right">
<a-tooltip :content="$t('sys.login.changeCode')" placement="bottom">
<img class="codeImg" :alt="$t('sys.login.changeCode')" :src="apiUrl + codeImgUrl" @click="handleChangeImg" />
</a-tooltip>
</a-col>
</a-row>
</FormItem>
<div class="pt-10px">
<FormItem class="enter-x">
<Button type="primary" size="large" block @click="handleLogin" :loading="loading">
{{ t('sys.login.loginButton') }}
</Button>
</FormItem>
</div>
</Form>
<QrCodeForm v-if="activeTab == 2" />
<div class="socials-box" v-if="socialsList.length">
<a-divider>{{ t('sys.login.otherLogin') }}</a-divider>
<div class="socials-list">
<a-tooltip :title="item.name + '登录'" v-for="(item, i) in socialsList" :key="i">
<div class="socials-item" @click="handleOtherLogin(item.enname)"><i :class="item.icon"></i></div>
</a-tooltip>
</div>
</div>
</div>
<a-button type="primary" class="sso-login-btn enter-x" size="large" :loading="loading" @click="handleSsoLogin" v-show="isSso && !ssoLoading">登录</a-button>
<BasicModal v-bind="$attrs" @register="registerSsoModal" title="登录" :footer="null" :width="1000" class="yunzhupaas-sso-modal" :closeFunc="onSsoModalClose">
<iframe width="100%" height="100%" :src="ssoUrl" frameborder="0" v-if="showIframe"></iframe>
</BasicModal>
<BasicModal v-bind="$attrs" @register="registerTenantSocialModal" :closable="false" :footer="null" :width="700" class="yunzhupaas-tenant-social-modal">
<div class="other-main">
<div class="other-title">
<div class="other-icon"><i class="icon-ym icon-ym-user"></i></div>
<div class="other-text">请选择登录账号</div>
</div>
<div class="other-body">
<a-row :gutter="20">
<a-col :span="12" v-for="(item, i) in tenantSocialList" :key="i">
<div @click="handleSocialLogin(item)">
<a-card hoverable class="other-login-card">
<div class="other-login-des other-login-title">{{ item.socialName }}</div>
<div class="other-login-des">租户名称{{ item.tenantName }}</div>
<div class="other-login-des">租户ID{{ item.tenantId }}</div>
<div class="other-login-des">账号ID{{ item.accountName }}</div>
</a-card>
</div>
</a-col>
</a-row>
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, toRefs, onUnmounted, nextTick } from 'vue';
import { Form, Input, Button } from 'ant-design-vue';
import { useI18n } from '@/hooks/web/useI18n';
import { useGlobSetting } from '@/hooks/setting';
import { useMessage } from '@/hooks/web/useMessage';
import { useUserStore } from '@/store/modules/user';
import { useFormRules, useFormValid } from './useLogin';
import { encryptByMd5 } from '@/utils/cipher';
import { onKeyStroke } from '@vueuse/core';
import { getLoginConfig, getTicket, getTicketStatus, socialsLogin, getConfig } from '@/api/basic/user';
import { createLocalStorage } from '@/utils/cache';
import { useRouter, useRoute } from 'vue-router';
import { PageEnum } from '@/enums/pageEnum';
import { BasicModal, useModal } from '@/components/Modal';
import { isString } from '@/utils/is';
import { AesEncryption } from '@/utils/cipher';
import QrCodeForm from './QrCodeForm.vue';
interface State {
formData: any;
isSso: boolean;
ssoLoading: boolean;
preUrl: string;
ssoUrl: string;
ssoTicket: string;
ticketParams: string;
socialsList: any[];
socialsWinUrl: any;
redirectUrl: string;
ssoTimer: any;
tenantSocialList: any[];
showIframe: boolean;
needCode: boolean;
codeLength: number;
timestamp: number;
codeImgUrl: string;
activeTab: number;
redirect: string;
}
const FormItem = Form.Item;
const InputPassword = Input.Password;
const { t } = useI18n();
const { createMessage } = useMessage();
const userStore = useUserStore();
const ls = createLocalStorage();
const router = useRouter();
const route = useRoute();
const query = route.query;
const globSetting = useGlobSetting();
const apiUrl = ref(globSetting.apiUrl);
const [registerSsoModal, { openModal: openSsoModal, closeModal: closeSsoModal }] = useModal();
const [registerTenantSocialModal, { openModal: openTenantSocialModal }] = useModal();
const { getFormRules } = useFormRules();
const formRef = ref();
const loading = ref(false);
const aesEncryption = new AesEncryption({ useHex: true });
let socialsWinUrl: any = null;
const state = reactive<State>({
formData: {
account: '',
password: '',
code: '',
origin: 'password',
},
isSso: false,
ssoLoading: true,
preUrl: '',
ssoUrl: '',
ssoTicket: '',
ticketParams: '',
socialsList: [],
socialsWinUrl: null,
redirectUrl: '',
ssoTimer: null,
tenantSocialList: [],
showIframe: false,
needCode: false,
codeLength: 4,
timestamp: 0,
codeImgUrl: '',
activeTab: 1,
redirect: '',
});
const { formData, socialsList, isSso, ssoLoading, ssoUrl, showIframe, tenantSocialList, codeImgUrl, activeTab } = toRefs(state);
const { validForm } = useFormValid(formRef);
onKeyStroke('Enter', handleLogin);
async function handleLogin() {
if (loading.value) return;
const data = await validForm();
if (!data) return;
try {
loading.value = true;
const password = encryptByMd5(data.password);
const encryptPassword = aesEncryption.encryptByAES(password);
const userInfo = await userStore.login({
account: data.account,
password: encryptPassword,
code: state.formData.code,
origin: state.formData.origin,
timestamp: state.timestamp,
yunzhupaas_ticket: state.ssoTicket,
});
if (!userInfo) {
if (state.needCode) {
state.formData.code = '';
handleChangeImg();
}
loading.value = false;
return;
}
// ====================== ✅ 存储账号 ======================
localStorage.setItem('loginAccount', data.account);
// ========================================================
router.replace(state.redirect || PageEnum.BASE_HOME);
} catch (error) {
if (isString(error)) createMessage.error(error);
if (state.needCode) {
state.formData.code = '';
handleChangeImg();
}
loading.value = false;
}
}
function handleGetLoginConfig() {
state.ssoLoading = true;
getLoginConfig()
.then(res => {
state.isSso = !!res.data.redirect;
state.preUrl = res.data.url;
state.ticketParams = res.data.ticketParams;
state.socialsList = res.data.socialsList || [];
state.ssoLoading = false;
ls.set('useSocials', state.socialsList.length, null);
})
.catch(() => {
state.isSso = false;
state.ssoLoading = false;
});
}
function handleOtherLogin(enname) {
getTicket().then(res => {
state.ssoTicket = res.data;
if (socialsWinUrl && !socialsWinUrl.closed) {
socialsWinUrl.location.replace(state.redirectUrl);
socialsWinUrl.focus();
return;
}
state.socialsList.forEach(item => {
if (enname == item.enname) {
const renderUrl = item.renderUrl.replace('YUNZHUPAAS_TICKET', state.ssoTicket);
state.redirectUrl = renderUrl;
}
});
const iWidth = 750; //弹出窗口的宽度;
const iHeight = 500; //弹出窗口的高度;
const iLeft = (window.screen.width - iWidth) / 2;
const iTop = (window.screen.height - iHeight) / 2; //获得窗口的垂直位置;
socialsWinUrl = window.open(
state.redirectUrl,
'_blank',
`height=${iHeight},innerHeight=${iHeight},width=${iWidth},innerWidth=${iWidth},top=${iTop},left=${iLeft},toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no`,
);
state.ssoTimer = setInterval(() => {
if (socialsWinUrl.closed) clearTimer();
handleGetTicketStatus();
}, 1000);
});
}
function clearTimer() {
if (!state.ssoTimer) return;
clearInterval(state.ssoTimer);
state.ssoTimer = null;
state.ssoTicket = '';
}
function handleGetTicketStatus() {
if (!state.ssoTicket) return;
getTicketStatus(state.ssoTicket).then(res => {
if (res.data.status != 2) {
socialsWinUrl && socialsWinUrl.close();
if (res.data.status == 4) {
//未绑定预留ticket
clearInterval(state.ssoTimer);
state.ssoTimer = null;
} else {
clearTimer();
}
switch (res.data.status) {
case 1: //登陆成功
userStore.updateToken(res.data.value);
nextTick(() => {
router.push(state.redirect || PageEnum.BASE_HOME);
});
break;
case 4: //未绑定
createMessage.error('第三方账号未绑定5分钟内登录本系统账号密码自动绑定该账号');
closeSsoModal();
state.ssoUrl = '';
break;
case 6: //多租户绑定多个
state.tenantSocialList = typeof res.data.value === 'string' ? JSON.parse(res.data.value) : res.data.value;
openTenantSocialModal(true);
break;
case 7: //免登
createMessage.error('第三方账号未绑定账号,请绑定后重试!');
break;
default:
createMessage.error(res.data.value || '账号异常!');
closeSsoModal();
state.ssoUrl = '';
getLoginConfig();
break;
}
}
});
}
function handleSsoLogin() {
if (loading.value) return;
loading.value = true;
getTicket().then(res => {
state.ssoTicket = res.data;
state.ssoUrl = state.preUrl + '?' + state.ticketParams + '=' + state.ssoTicket;
openSsoModal(true);
state.showIframe = true;
state.ssoTimer = setInterval(() => {
handleGetTicketStatus();
}, 1000);
});
}
async function onSsoModalClose() {
loading.value = false;
state.showIframe = false;
clearTimer();
return true;
}
function handleSocialLogin(data) {
socialsLogin({ ...data, tenantLogin: true }).then(res => {
userStore.updateToken(res.data.token);
nextTick(() => {
router.push(state.redirect || PageEnum.BASE_HOME);
});
});
}
function onAccountChange(e) {
const value = e.target.value;
if (!value) return;
handleGetConfig(value);
}
function handleGetConfig(value) {
getConfig(value).then(res => {
state.needCode = !!res.data.enableVerificationCode;
if (state.needCode) {
state.codeLength = res.data.verificationCodeNumber || 4;
handleChangeImg();
}
});
}
function handleChangeImg() {
const timestamp = Math.random();
state.timestamp = timestamp;
state.codeImgUrl = `/api/oauth/ImageCode/${state.codeLength || 4}/${timestamp}`;
}
onMounted(() => {
if (state.formData.account) handleGetConfig(state.formData.account);
if (state.needCode) handleChangeImg();
state.redirect = (query?.redirect as string) || '';
if (query?.YUNZHUPAAS_TICKET) {
state.ssoTicket = query.YUNZHUPAAS_TICKET as string;
handleGetTicketStatus();
}
handleGetLoginConfig();
});
onUnmounted(() => {
clearTimer();
});
</script>

View File

@@ -0,0 +1,17 @@
<template>
<div>
<a-image class="login-logo" :src="apiUrl + getLoginIcon" :fallback="loginLogo" :preview="false" v-if="getLoginIcon" />
<img class="login-logo" :src="loginLogo" v-else />
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { Image as AImage } from 'ant-design-vue';
import { useGlobSetting } from '@/hooks/setting';
import loginLogo from '@/assets/images/login_logo.png';
const globSetting = useGlobSetting();
const apiUrl = ref(globSetting.apiUrl);
const getLoginIcon = computed(() => localStorage.getItem('_APP_LOGIN_LOGO_') || '');
</script>

View File

@@ -0,0 +1,113 @@
<template>
<div class="qrcode-form enter-x">
<div class="qrcode-title">{{ t('sys.login.qrCodeTip') }}</div>
<div class="qrcode-content">
<QRCode :value="qrCodeText" :size="240" :bordered="false" bgColor="#ffffff" color="#000" />
<div class="qrcode-mask" v-if="qrCodeStatus !== 'active'">
<div class="qrcode-mask-main" v-loading="qrCodeStatus === 'loading'">
<div class="qrcode-scanned" v-if="qrCodeStatus === 'scanned'">
<div class="qrcode-icon">
<CheckOutlined />
</div>
<p class="qrcode-tip">{{ t('sys.login.scanSuccessful') }}</p>
<p class="qrcode-tip">{{ t('sys.login.confirmLogin') }}</p>
</div>
<div class="qrcode-expired" v-if="qrCodeStatus === 'expired'">
<div class="qrcode-icon expired-icon">
<ExclamationOutlined />
</div>
<p class="qrcode-tip">{{ t('sys.login.expired') }}</p>
<p class="qrcode-tip link-text" @click="reset">{{ t('sys.login.refreshCode') }}</p>
</div>
</div>
</div>
<div class="qrcode-bottom" v-if="qrCodeStatus === 'scanned'">
<span class="link-text" @click="goBack">{{ t('sys.login.recoverCode') }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onMounted, onUnmounted, nextTick } from 'vue';
import { QRCode } from 'ant-design-vue';
import { CheckOutlined, ExclamationOutlined } from '@ant-design/icons-vue';
import { getCodeCertificate, getCodeCertificateStatus, setCodeCertificateStatus } from '@/api/basic/user';
import { useUserStore } from '@/store/modules/user';
import { useRouter } from 'vue-router';
import { PageEnum } from '@/enums/pageEnum';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
qrCodeText: string;
qrCodeStatus: string;
ticket: string;
timer: any;
}
const { t } = useI18n();
const userStore = useUserStore();
const router = useRouter();
const state = reactive<State>({
qrCodeText: '',
// active/expired/scanned/loading
qrCodeStatus: 'loading',
ticket: '',
timer: null,
});
const { qrCodeText, qrCodeStatus } = toRefs(state);
function reset() {
clearTimer();
state.ticket = '';
state.qrCodeStatus = 'loading';
getCodeCertificate().then(res => {
state.ticket = res?.data || '';
if (!res?.data) return;
const data = { t: 'login', id: res.data };
state.qrCodeText = JSON.stringify(data);
state.qrCodeStatus = 'active';
state.timer = setInterval(() => {
handleGetStatus();
}, 1000);
});
}
function handleGetStatus() {
getCodeCertificateStatus(state.ticket).then(res => {
if (res.data.status === 0) return;
if (res.data.status === 1) {
state.qrCodeStatus = 'scanned';
return;
}
clearTimer();
if (res.data.status === -1) {
state.qrCodeStatus = 'expired';
return;
}
// 登录成功
if (res.data.status === 2) {
userStore.updateToken(res.data.value);
nextTick(() => {
router.push(PageEnum.BASE_HOME);
});
return;
}
});
}
function goBack() {
setCodeCertificateStatus(state.ticket, '-1').then(() => {
reset();
});
}
function clearTimer() {
if (!state.timer) return;
clearInterval(state.timer);
state.timer = null;
}
onMounted(() => {
reset();
});
onUnmounted(() => {
clearTimer();
});
</script>

View File

@@ -0,0 +1,45 @@
<template>
<transition>
<div :class="prefixCls">
<Login sessionTimeout />
</div>
</transition>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue';
import Login from './Login.vue';
import { useDesign } from '@/hooks/web/useDesign';
import { useUserStore } from '@/store/modules/user';
import { usePermissionStore } from '@/store/modules/permission';
const { prefixCls } = useDesign('st-login');
const userStore = useUserStore();
const permissionStore = usePermissionStore();
const userId = ref<Nullable<number | string>>(0);
onMounted(() => {
// 记录当前的UserId
userId.value = userStore.getUserInfo?.userId;
});
onBeforeUnmount(() => {
if (userId.value && userId.value !== userStore.getUserInfo.userId) {
// 登录的不是同一个用户,刷新整个页面以便丢弃之前用户的页面状态
document.location.reload();
} else if (permissionStore.getLastBuildMenuTime === 0) {
// 后台权限模式下没有成功加载过菜单就重新加载整个页面。这通常发生在会话过期后按F5刷新整个页面后载入了本模块这种场景
document.location.reload();
}
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-st-login';
.@{prefix-cls} {
position: fixed;
z-index: 9999999;
width: 100%;
height: 100%;
background: @component-background;
}
</style>

View File

@@ -0,0 +1,466 @@
@prefix-cls: ~'@{namespace}-login-container';
@countdown-prefix-cls: ~'@{namespace}-countdown-input';
@dark-bg: #293146;
html[data-theme='dark'] {
.@{prefix-cls} {
background-image: url(@/assets/images/login-bg-dark.png);
.login-content {
background-color: @dark-bg;
box-shadow: 0px 40px 40px rgba(11, 15, 19, 0.2);
.login-left::after {
background-color: #343434;
}
.login-sub-title,
.rule-tip {
color: #606266;
}
.login-cap {
color: #ffffff;
}
}
.ant-input,
.ant-input-affix-wrapper,
.ant-input-password {
background-color: #232a3b;
}
.ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
border: 1px solid #4a5569;
}
&-form {
background-color: @dark-bg !important;
}
.code-box {
.code {
background: #3333 !important;
}
}
input.fix-auto-fill,
.fix-auto-fill input {
-webkit-box-shadow: 0 0 0 1000px #232a3b inset !important;
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px #232a3b inset !important;
-webkit-text-fill-color: #c9d1d9 !important;
}
.ant-input-affix-wrapper > input.ant-input:focus {
box-shadow: 0 0 0 1000px #232a3b inset !important;
-webkit-text-fill-color: #c9d1d9 !important;
caret-color: #c9d1d9;
border-color: unset !important;
}
input:-internal-autofill-previewed,
input:-internal-autofill-selected {
-webkit-text-fill-color: #232a3b;
transition: background-color 5000s ease-out 0.2s;
}
.qrcode-form .qrcode-mask {
.qrcode-mask-main {
background: rgba(41, 49, 70, 0.96);
}
}
}
}
.@{prefix-cls} {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
display: flex;
justify-content: center;
align-items: center;
background-image: url(@/assets/images/login-bg.png);
background-position: center center;
background-repeat: no-repeat;
background-attachment: scroll;
background-size: cover;
background-origin: border-box;
.ant-input-affix-wrapper > input.ant-input:focus {
box-shadow: 0 0 0 1000px #fff inset !important;
}
.login-version {
position: fixed;
right: 0px;
top: 0px;
width: 82px;
height: 82px;
background: url('@/assets/images/login_version.png') no-repeat center;
background-size: 100%;
.login-version-text {
width: 82px;
height: 82px;
line-height: 50px;
text-align: center;
color: #fff;
font-size: 16px;
transform: rotate(45deg);
white-space: nowrap;
overflow: hidden;
}
}
.@{prefix-cls}-form {
width: 500px;
padding: 73px 50px 20px;
.ant-image,
.login-logo {
width: 100%;
height: 36px;
margin: 0 auto 20px;
}
}
.login-left {
height: 100%;
position: relative;
width: 500px;
padding-top: 80px;
position: relative;
&::after {
content: '';
display: block;
width: 1px;
height: 420px;
background-color: @border-color-base1;
position: absolute;
right: 0;
top: 90px;
}
.ant-image,
.login-logo {
display: block;
width: 400px;
height: 36px;
margin: 0 auto 50px;
}
.login-banner {
display: block;
margin: 0 auto;
width: 400px;
height: auto;
}
}
.copyright {
color: #656e93;
font-size: 14px;
position: fixed;
bottom: 50px;
text-align: center;
}
.login-header {
position: absolute;
top: 80px;
right: 60px;
left: 60px;
display: flex;
align-items: center;
justify-content: space-between;
.login-company-logo {
display: block;
.login-company-logo-img {
height: 36px;
width: auto;
}
}
}
.login-content {
height: 580px;
border-radius: 8px;
box-shadow: 0px 40px 40px rgba(141, 150, 160, 0.1);
display: flex;
justify-content: center;
align-items: center;
position: relative;
background: @component-background;
z-index: 1;
overflow: hidden;
.login-cap {
font-size: 24px;
line-height: 33px;
margin-bottom: 8px;
}
.login-sub-title {
margin-bottom: 20px;
line-height: 17px;
color: #8c8c8c;
height: 17px;
user-select: none;
font-size: 14px;
span {
color: @primary-color;
cursor: pointer;
}
}
.login-tab {
margin-bottom: 60px;
display: flex;
justify-content: center;
align-items: center;
.login-tab-item {
cursor: pointer;
font-size: 16px;
line-height: 46px;
color: @text-color-secondary;
padding: 0 30px;
position: relative;
&.active {
font-size: 20px;
font-weight: 600;
color: @text-color-base;
&::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 3px;
background-color: @primary-color;
}
}
}
}
.sso-login-btn {
width: 100%;
font-size: 16px;
margin-top: 100px;
}
.code-box {
z-index: 100;
width: 400px;
position: absolute;
bottom: 50px;
right: 50px;
.code-floor {
display: flex;
align-items: center;
justify-content: space-between;
&.code-floor1 {
margin-bottom: 12px;
}
.code {
width: 120px;
height: 32px;
background: @component-background;
border: 1px solid #93a9c6;
opacity: 1;
border-radius: 2px;
cursor: pointer;
display: flex;
align-items: center;
padding: 0 10px;
&:hover {
background: @primary-color;
border: 1px solid @primary-color;
.code-icon {
color: #fff;
}
.code-txt {
color: #fff;
}
}
.code-icon {
flex-shrink: 0;
font-size: 20px;
color: #93a9c6;
width: 20px;
}
.code-txt {
text-align: center;
font-size: 14px;
color: #93a9c6;
flex: 1;
}
}
}
}
}
.socials-box {
position: absolute;
padding: 0 50px;
bottom: 50px;
right: 0;
left: 0;
}
.socials-list {
display: flex;
align-items: center;
justify-content: center;
.socials-item {
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
cursor: pointer;
border-radius: 50%;
margin: 0 12px;
i {
font-size: 22px;
color: #b9b9b9;
}
&:hover {
background-color: @primary-color;
i {
color: #fff;
}
}
}
}
.sms-input {
width: 260px;
overflow: hidden;
.ant-input {
width: 260px;
min-width: 0 !important;
}
}
.sms-right {
width: 120px;
height: 40px;
cursor: pointer;
.codeImg {
width: 100%;
height: 40px;
}
.smsBtn {
width: 100%;
}
}
.rule-tip {
color: #8c8c8c;
font-size: 12px;
line-height: 12px;
text-align: left;
.ant-form-item-control-input {
line-height: 12px !important;
min-height: 12px !important;
}
}
.qrcode-form {
position: relative;
padding-top: 20px;
.qrcode-title {
font-size: 14px;
text-align: center;
margin-bottom: 27px;
}
.qrcode-content {
position: relative;
width: 260px;
height: 260px;
padding: 10px;
margin: 0 auto;
background: url(../../../assets/images/qrcode-bg.png) 100% 100% no-repeat;
background-size: cover;
.ant-qrcode {
border-radius: 0;
}
}
.qrcode-mask {
position: absolute;
left: 10px;
right: 10px;
top: 10px;
display: flex;
justify-content: center;
align-items: center;
.qrcode-mask-main {
position: relative;
width: 240px;
height: 240px;
background: rgba(255, 255, 255, 0.96);
text-align: center;
padding-top: 70px;
.qrcode-icon {
background-color: @primary-color;
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto 20px;
display: flex;
justify-content: center;
align-items: center;
font-size: 40px;
color: #fff;
&.expired-icon {
background-color: @error-color;
}
}
.qrcode-tip {
font-size: 20px;
}
}
}
.qrcode-bottom {
text-align: center;
margin-top: 10px;
}
.link-text {
font-size: 16px !important;
}
}
}
.login-code-popover {
.ant-popover-inner-content {
padding: 12px;
}
.code-content {
padding: 0;
.qrcode {
display: block;
width: 122px;
height: 122px;
}
.code-tip {
text-align: center;
font-size: 14px;
font-weight: 400;
line-height: 25px;
color: @text-color-label;
}
}
}
.yunzhupaas-login-code-modal {
.ant-modal-header {
border-bottom: none !important;
height: 10px !important;
}
.wechat-code-container {
text-align: center;
padding-bottom: 30px;
.cap {
line-height: 38px;
font-size: 26px;
color: #000721;
}
.wechat-code-img {
display: inline-block;
width: 182px;
height: 182px;
margin: 40px 0 44px;
}
.tip {
line-height: 30px;
font-size: 16px;
color: #666;
margin-bottom: 6px;
font-weight: 400;
}
}
}

View File

@@ -0,0 +1,40 @@
<template>
<div class="box" v-loading="loading"></div>
</template>
<script lang="ts" setup>
import { ref, onMounted, nextTick } from 'vue';
import { PageEnum } from '@/enums/pageEnum';
import { useRouter, useRoute } from 'vue-router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'ssoRedirect' });
const loading = ref(true);
const userStore = useUserStore();
function init() {
const route = useRoute();
const router = useRouter();
const token = route.query.token;
const redirect = route.query.redirect;
if (!token) return;
userStore.updateToken(token as string);
nextTick(() => {
router.replace((redirect as string) || PageEnum.BASE_HOME);
loading.value = false;
});
}
onMounted(() => {
init();
});
</script>
<style lang="less" scoped>
.box {
width: 100%;
height: 100%;
position: relative;
}
</style>

View File

@@ -0,0 +1,124 @@
import type { FormInstance } from 'ant-design-vue';
import type { Rule, RuleObject, NamePath } from 'ant-design-vue/lib/form/interface';
import { ref, computed, unref, Ref } from 'vue';
import { useI18n } from '@/hooks/web/useI18n';
export enum LoginStateEnum {
LOGIN,
REGISTER,
RESET_PASSWORD,
MOBILE,
QR_CODE,
}
const currentState = ref(LoginStateEnum.LOGIN);
export function useLoginState() {
function setLoginState(state: LoginStateEnum) {
currentState.value = state;
}
const getLoginState = computed(() => currentState.value);
function handleBackLogin() {
setLoginState(LoginStateEnum.LOGIN);
}
return { setLoginState, getLoginState, handleBackLogin };
}
export function useFormValid<T extends Object = any>(formRef: Ref<FormInstance>) {
const validate = computed(() => {
const form = unref(formRef);
return form?.validate ?? ((_nameList?: NamePath) => Promise.resolve());
});
async function validForm() {
const form = unref(formRef);
if (!form) return;
const data = await form.validate();
return data as T;
}
return { validate, validForm };
}
export function useFormRules(formData?: Recordable) {
const { t } = useI18n();
const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder')));
const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder')));
const getCodeFormRule = computed(() => createRule(t('sys.login.codeTip')));
const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder')));
const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder')));
const validatePolicy = async (_: RuleObject, value: boolean) => {
return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve();
};
const validateConfirmPassword = (password: string) => {
return async (_: RuleObject, value: string) => {
if (!value) {
return Promise.reject(t('sys.login.passwordPlaceholder'));
}
if (value !== password) {
return Promise.reject(t('sys.login.diffPwd'));
}
return Promise.resolve();
};
};
const getFormRules = computed((): { [k: string]: Rule | Rule[] } => {
const accountFormRule = unref(getAccountFormRule);
const passwordFormRule = unref(getPasswordFormRule);
const codeFormRule = unref(getCodeFormRule);
const smsFormRule = unref(getSmsFormRule);
const mobileFormRule = unref(getMobileFormRule);
const mobileRule = {
sms: smsFormRule,
mobile: mobileFormRule,
};
switch (unref(currentState)) {
// register form rules
case LoginStateEnum.REGISTER:
return {
account: accountFormRule,
password: passwordFormRule,
confirmPassword: [{ validator: validateConfirmPassword(formData?.password), trigger: 'change' }],
policy: [{ validator: validatePolicy, trigger: 'change' }],
...mobileRule,
};
// reset password form rules
case LoginStateEnum.RESET_PASSWORD:
return {
account: accountFormRule,
...mobileRule,
};
// mobile form rules
case LoginStateEnum.MOBILE:
return mobileRule;
// login form rules
default:
return {
account: accountFormRule,
password: passwordFormRule,
code: codeFormRule,
};
}
});
return { getFormRules };
}
function createRule(message: string): RuleObject[] {
return [
{
required: true,
message,
trigger: 'change',
},
];
}

View File

@@ -0,0 +1,175 @@
<template>
<div class="yunzhupaas-content-wrapper message-record-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box">
<BasicForm class="search-form" @register="registerForm" @submit="handleSubmit" @reset="handleReset" />
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<a-tabs v-model:activeKey="activeKey" class="yunzhupaas-content-wrapper-tabs" destroyInactiveTabPane>
<a-tab-pane v-for="item in messageType" :key="item.enCode" :tab="item.fullName"></a-tab-pane>
</a-tabs>
<BasicTable @register="registerTable" :columns="columns" :searchInfo="getSearchInfo">
<template #tableTitle>
<a-button type="error" preIcon="icon-ym icon-ym-btn-clearn" @click="handleDelete">{{ t('common.delText') }}</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'">
<a :title="record.name" @click="handleView(record)">{{ record.title }}</a>
</template>
<template v-if="column.key === 'type'">
{{ getTypeName(record.type) }}
</template>
<template v-if="column.key === 'isRead'">
<a-tag :color="record.isRead == 1 ? 'success' : ''">{{ record.isRead == 1 ? '已读' : '未读' }}</a-tag>
</template>
</template>
</BasicTable>
</div>
</div>
<Detail @register="registerDetail" />
<ScheduleDetail @register="registerScheduleDetail" />
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, watch, computed, nextTick, onMounted } from 'vue';
import { getMessageList, readInfo, delMsgRecord } from '@/api/system/message';
import { BasicForm, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicTable, useTable, BasicColumn } from '@/components/Table';
import { useModal } from '@/components/Modal';
import { useRouter } from 'vue-router';
import { encryptByBase64 } from '@/utils/cipher';
import { useBaseStore } from '@/store/modules/base';
import Detail from '@/views/system/notice/Detail.vue';
import ScheduleDetail from '@/views/workFlow/schedule/Detail.vue';
import { getScheduleDetail } from '@/api/workFlow/schedule';
defineOptions({ name: 'messageRecord' });
interface State {
activeKey: string;
keyword: string;
messageType: any[];
}
const router = useRouter();
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const state = reactive<State>({
activeKey: '0',
keyword: '',
messageType: [],
});
const { activeKey, messageType } = toRefs(state);
const baseStore = useBaseStore();
const getSearchInfo = computed(() => ({ keyword: state.keyword, type: state.activeKey == '0' ? '' : state.activeKey }));
const [registerForm, { resetFields }] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
],
});
const columns: BasicColumn[] = [
{ title: '消息标题', dataIndex: 'title' },
{ title: '消息类型', dataIndex: 'type', width: 120 },
{ title: '发送人员', dataIndex: 'releaseUser', width: 120 },
{ title: '发送时间', dataIndex: 'releaseTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '状态', dataIndex: 'isRead', width: 70 },
];
const [registerTable, { reload, getSelectRows, clearSelectedRowKeys }] = useTable({
api: getMessageList,
rowSelection: { type: 'checkbox' },
clickToRowSelect: false,
immediate: false,
});
const [registerDetail, { openModal: openDetailModal }] = useModal();
const [registerScheduleDetail, { openModal: openScheduleDetailModal }] = useModal();
watch(
() => state.activeKey,
() => {
resetFields();
},
);
async function initMessageType() {
const all = { id: '', fullName: '全部', enCode: '' };
const list = ((await baseStore.getDictionaryData('msgSourceType')) as any[]) || [];
state.messageType = [all, ...list];
}
function handleSubmit(values) {
state.keyword = values?.keyword || '';
handleSearch();
}
function handleReset() {
state.keyword = '';
handleSearch();
}
function handleSearch() {
nextTick(() => {
reload({ page: 1 });
});
}
function handleDelete() {
const list: any[] = getSelectRows();
if (!list.length) return createMessage.error(t('common.selectDataTip'));
const query = { ids: list.map(item => item.id).join(',') };
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: t('common.batchDelTip'),
onOk: () => {
delMsgRecord(query).then(res => {
createMessage.success(res.msg);
clearSelectedRowKeys();
reload();
});
},
});
}
function handleView(item) {
readInfo(item.id).then(res => {
if (item.isRead == '0') item.isRead = '1';
if (item.type == 4) {
let bodyText = res.data.bodyText ? JSON.parse(res.data.bodyText) : {};
if (bodyText.type == 3) return;
getScheduleDetail(bodyText.groupId, bodyText.id).then(() => {
openScheduleDetailModal(true, { id: bodyText.id, groupId: bodyText.groupId });
});
} else if (item.type == 2 && item.flowType == 2) {
const bodyText = JSON.parse(res.data.bodyText);
if (bodyText.type == 0) return;
router.push('/profile?config=' + bodyText.type);
} else {
if (item.type == 1 || item.type == 3) {
openDetailModal(true, { id: item.id, type: 1 });
} else {
if (!res.data.bodyText) return;
router.push('/workFlowDetail?config=' + encodeURIComponent(encryptByBase64(res.data.bodyText)));
}
}
});
}
function getTypeName(type?) {
const list = state.messageType.filter(o => o.enCode == type) || [];
return list.length ? list[0].fullName : '公告';
}
onMounted(() => {
initMessageType();
state.activeKey = '';
});
</script>

View File

@@ -0,0 +1,69 @@
<template>
<div class="authorize">
<a-tabs v-model:activeKey="activeKey" class="auth-tabs">
<a-tab-pane tab="菜单权限" key="module"></a-tab-pane>
<a-tab-pane tab="按钮权限" key="button"></a-tab-pane>
<a-tab-pane tab="列表权限" key="column"></a-tab-pane>
<a-tab-pane tab="表单权限" key="form"></a-tab-pane>
<a-tab-pane tab="数据权限" key="resource"></a-tab-pane>
<a-tab-pane tab="门户权限" key="portal"></a-tab-pane>
<a-tab-pane tab="流程权限" key="flow"></a-tab-pane>
<a-tab-pane tab="打印模板权限" key="print"></a-tab-pane>
</a-tabs>
<div class="auth-tree">
<BasicTree :treeData="state.treeData" :loading="loading" defaultExpandAll :key="key" />
</div>
</div>
</template>
<script setup lang="ts">
import { getAuthorizeList } from '@/api/permission/userSetting';
import { reactive, toRefs, onMounted, watch } from 'vue';
import { BasicTree } from '@/components/Tree';
interface State {
activeKey: string;
authData: any;
loading: boolean;
treeData: any[];
key: number;
}
const state = reactive<State>({
activeKey: 'module',
authData: {},
loading: false,
treeData: [],
key: +new Date(),
});
const { activeKey, loading, key } = toRefs(state);
watch(
() => state.activeKey,
val => {
state.treeData = state.authData[val] || [];
state.key = +new Date();
},
);
function init() {
state.loading = true;
getAuthorizeList().then(res => {
state.authData = res.data;
state.treeData = state.authData[state.activeKey] || [];
state.loading = false;
});
}
onMounted(() => {
init();
});
</script>
<style lang="less" scoped>
.authorize {
height: 100%;
.auth-tree {
height: calc(100% - 64px);
overflow: auto;
}
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
<BasicForm @register="registerForm" :schemas="getSchemas">
<template #toUserId="{ model, field }">
<YunzhupaasUserSelect
v-model:value="model[field]"
multiple
@change="onToUserIdChange"
v-if="getSysConfig[state.dataForm.type === 0 ? 'delegateScope' : 'proxyScope'] === 1" />
<UserSelect
v-model:value="model[field]"
multiple
@change="onToUserIdChange"
:query="{ type: getSysConfig[state.dataForm.type === 0 ? 'delegateScope' : 'proxyScope'] }"
:api="getReceiveUserList"
v-else />
</template>
<template #flowId>
<flow-select
v-model:value="state.flowId"
popupTitle="委托流程"
:entrustType="state.dataForm.type"
:toUserId="state.dataForm.toUserId"
placeholder="全部流程"
@change="onFlowIdChange" />
</template>
</BasicForm>
</BasicModal>
</template>
<script lang="ts" setup>
import { getInfo, create, update } from '@/api/workFlow/flowDelegate';
import { computed, reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import dayjs, { Dayjs } from 'dayjs';
import FlowSelect from '@/views/workFlow/components/FlowSelect.vue';
import { useI18n } from '@/hooks/web/useI18n';
import { YunzhupaasUserSelect } from '../../../../../components/Yunzhupaas/Organize/index';
import { useAppStore } from '@/store/modules/app';
import { UserSelect } from '@/components/CommonModal';
import { getReceiveUserList } from '@/api/permission/user';
const emit = defineEmits(['register', 'reload']);
const disabledDate = (current: Dayjs) => current && current < dayjs().endOf('day').subtract(1, 'day');
const checkStartTime = async (_rule, value) => {
if (!getFieldsValue().endTime) return Promise.resolve();
if (getFieldsValue().endTime < value) return Promise.reject('开始时间应该小于结束时间');
validate(['endTime']);
return Promise.resolve();
};
const checkEndTime = async (_rule, value) => {
if (!getFieldsValue().startTime) return Promise.resolve();
if (getFieldsValue().startTime > value) return Promise.reject('结束时间应该大于开始时间');
return Promise.resolve();
};
const [registerForm, { setFieldsValue, resetFields, getFieldsValue, validate }] = useForm({
labelWidth: 90,
});
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
const state = reactive({
dataForm: {
id: '',
toUserName: '',
toUserId: [],
userName: '',
type: 0,
flowId: '',
flowName: '全部流程',
},
flowId: [],
});
const { createMessage } = useMessage();
const { t } = useI18n();
const appStore = useAppStore();
const getSysConfig = computed(() => appStore.getSysConfigInfo);
const getTitle = computed(() => (!state.dataForm.id ? t('common.addText') : t('common.editText')));
const getSchemas = computed(() => {
const title = state.dataForm.type === 0 ? '委托' : '代理';
const schemas: any[] = [
{
field: 'toUserId',
label: state.dataForm.type === 0 ? '受委托人' : '代理人',
component: 'UserSelect',
slot: 'toUserId',
rules: [{ required: true, trigger: 'blur', message: '必填', type: 'array' }],
},
{
field: 'flowId',
label: `${title}流程`,
helpMessage: `未选择${title}流程默认全部流程进行${title}`,
component: 'Input',
slot: 'flowId',
},
{
field: 'startTime',
label: '开始时间',
component: 'DatePicker',
componentProps: { format: 'YYYY-MM-DD HH:mm:ss', disabledDate },
rules: [
{ required: true, message: '必填', trigger: 'change' },
{ validator: checkStartTime, trigger: 'change' },
],
},
{
field: 'endTime',
label: '结束时间',
component: 'DatePicker',
componentProps: { format: 'YYYY-MM-DD HH:mm:ss', disabledDate },
rules: [
{ required: true, message: '必填', trigger: 'change' },
{ validator: checkEndTime, trigger: 'change' },
],
},
{
field: 'description',
label: `${title}说明`,
component: 'Textarea',
componentProps: { placeholder: '请输入' },
},
];
return schemas;
});
function init(data) {
changeLoading(true);
resetFields();
state.flowId = [];
state.dataForm = {
id: '',
toUserName: '',
toUserId: [],
userName: '',
type: 0,
flowId: '',
flowName: '全部流程',
};
state.dataForm.type = data.type || 0;
state.dataForm.id = data.id;
if (state.dataForm.id) {
getInfo(state.dataForm.id).then(res => {
setFieldsValue(res.data);
state.dataForm = res.data;
(state.flowId as string[]) = state.dataForm.flowId ? state.dataForm.flowId.split(',') : [];
changeLoading(false);
});
} else {
changeLoading(false);
}
}
function onToUserIdChange(id, data) {
if (!id) {
state.dataForm.toUserId = [];
state.dataForm.toUserName = '';
return;
}
state.dataForm.toUserId = id;
state.dataForm.toUserName = data.map(o => o.fullName).join();
}
function onFlowIdChange(_ids, data) {
if (!data || !data.length) return (state.dataForm.flowName = '全部流程');
state.dataForm.flowName = data.map(o => o.fullName + '/' + o.enCode).join();
}
async function handleSubmit() {
const values = await validate();
if (!values) return;
changeOkLoading(true);
const query = {
...values,
type: state.dataForm.type,
flowId: state.flowId.join(),
flowName: state.dataForm.flowName,
userName: state.dataForm.userName,
toUserName: state.dataForm.toUserName,
};
const formMethod = state.dataForm.id ? update : create;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
closeModal();
emit('reload');
})
.catch(() => {
changeOkLoading(false);
});
}
</script>

View File

@@ -0,0 +1,285 @@
<template>
<div class="yunzhupaas-content-wrapper mt-10px">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box">
<BasicForm class="search-form" @register="registerSearchForm" @submit="handleSubmit" @reset="handleReset"></BasicForm>
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<a-tabs v-model:activeKey="activeKey" class="yunzhupaas-content-wrapper-tabs" destroyInactiveTabPane>
<a-tab-pane :key="item.id" :tab="item.fullName" v-for="item in tabList"></a-tab-pane>
</a-tabs>
<BasicTable @register="registerMyEntrustTable" :searchInfo="getSearchInfo" :columns="getColumns">
<template #headerTop v-if="activeKey == 1 || activeKey == 3">
<a-alert
:message="activeKey == 1 ? '委托是指允许受委托人代替委托人在系统中发起流程。' : '代理是指允许代理人代替被代理人在系统中处理流程审批。'"
showIcon
type="warning"
class="mt-12px" />
</template>
<template #tableTitle v-if="activeKey == 1 || activeKey == 3">
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">新建</a-button>
</template>
<template #expandedRowRender="{ record }" v-if="activeKey == 1 || activeKey == 3">
<BasicTable @register="registerUserTable" :data-source="record.userList">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getConfirmStatusColor(record.status)">{{ getUserStatusContent(record.status) }}</a-tag>
</template>
</template>
</BasicTable>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">{{ getStatusContent(record.status) }}</a-tag>
</template>
<template v-if="column.key === 'confirmStatus'">
<a-tag :color="getConfirmStatusColor(record.confirmStatus)">{{ getConfirmStatusContent(record.confirmStatus) }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" />
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onMounted, computed, nextTick, watch } from 'vue';
import { getFlowDelegateList, del, stop, notarize, getFlowDelegateInfo } from '@/api/workFlow/flowDelegate';
import { BasicForm, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useModal } from '@/components/Modal';
import Form from './Form.vue';
import { useRoute } from 'vue-router';
interface State {
activeKey: number;
keyword: string;
userList: any[];
activeUser: string;
}
defineOptions({ name: 'workFlow-entrust' });
const { createMessage } = useMessage();
const { t } = useI18n();
const [registerForm, { openModal: openFormModal }] = useModal();
const state = reactive<State>({
activeKey: 1,
keyword: '',
userList: [],
activeUser: '',
});
const { activeKey } = toRefs(state);
const tabList = [
{ fullName: '我的委托', id: 1 },
{ fullName: '委托给我', id: 2 },
{ fullName: '我的代理', id: 3 },
{ fullName: '代理给我', id: 4 },
];
const [registerSearchForm, { resetFields }] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
],
});
const userColumns: BasicColumn[] = [
{ title: '', dataIndex: 'entrust', width: 38 },
{ title: '序号', dataIndex: 'index', width: 50, align: 'center', customRender: ({ index }) => index + 1 },
{ title: '受委托人', dataIndex: 'toUserName', width: 650 },
{ title: '流程状态', dataIndex: 'status', width: 120, align: 'center' },
{ title: '', dataIndex: 'flow' },
];
const [registerMyEntrustTable, { reload }] = useTable({
api: getFlowDelegateList,
actionColumn: {
width: 150,
title: '操作',
dataIndex: 'action',
},
immediate: false,
rowKey: 'yunzhupaasId',
afterFetch: data => {
const list = data.map(o => ({
...o,
yunzhupaasId: o.id + Math.random(),
}));
return list;
},
onExpand: handleExpand,
});
const [registerUserTable] = useTable({
columns: userColumns,
showHeader: false,
showTableSetting: false,
pagination: false,
showIndexColumn: false,
immediate: false,
});
watch(
() => state.activeKey,
() => {
resetFields();
},
);
const getSearchInfo = computed(() => ({ keyword: state.keyword, type: state.activeKey }));
const getColumns = computed(() => {
const myEntrustColumns: BasicColumn[] = [
{ title: '受委托人', dataIndex: 'toUserName', width: 200 },
{ title: '委托流程', dataIndex: 'flowName', width: 150 },
{ title: '开始时间', dataIndex: 'startTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '结束时间', dataIndex: 'endTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '状态', dataIndex: 'status', width: 120, align: 'center' },
{ title: '委托说明', dataIndex: 'description' },
];
const entrustColumns: BasicColumn[] = [
{ title: '委托人', dataIndex: 'userName', width: 200 },
{ title: '委托流程', dataIndex: 'flowName', width: 150 },
{ title: '开始时间', dataIndex: 'startTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '结束时间', dataIndex: 'endTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '生效状态', dataIndex: 'status', width: 120, align: 'center' },
{ title: '委托说明', dataIndex: 'description' },
{ title: '确认状态', dataIndex: 'confirmStatus', width: 120, align: 'center' },
];
if (state.activeKey === 1) return myEntrustColumns;
if (state.activeKey === 2) return entrustColumns;
if (state.activeKey === 3 || state.activeKey === 4) {
let list = state.activeKey === 3 ? myEntrustColumns : entrustColumns;
list[0].title = state.activeKey === 3 ? '代理人' : '被代理人';
list[1].title = '代理流程';
list[5].title = '代理说明';
return list;
}
});
function handleExpand(expanded, record) {
if (!expanded || record.userList?.length) return;
getFlowDelegateInfo(record.id).then(res => {
record.userList = res.data;
});
}
function getTableActions(record): ActionItem[] {
if (state.activeKey === 1 || state.activeKey === 3) {
return [
{
label: t('common.editText'),
disabled: record.status !== 0 || !record.isEdit,
onClick: addOrUpdateHandle.bind(null, record.id),
},
{
label: t('common.delText'),
color: 'error',
disabled: record.status === 1,
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
{
ifShow: record.status === 1,
label: '终止',
color: 'error',
modelConfirm: {
onOk: handleStop.bind(null, record.id),
content: '终止后,流程不再进行委托!',
},
},
];
} else {
return [
{
ifShow: record.status !== 2 && record.confirmStatus === 0,
label: '接受',
modelConfirm: {
onOk: handleAcceptOrReject.bind(null, record.id, 1),
content: '您确认要接受请求吗,是否继续?',
},
},
{
ifShow: record.status !== 2 && record.confirmStatus === 0,
label: '拒绝',
color: 'error',
modelConfirm: {
onOk: handleAcceptOrReject.bind(null, record.id, 2),
content: '您确认要拒绝请求吗,是否继续?',
},
},
];
}
}
function handleStop(id) {
stop(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleAcceptOrReject(id, type) {
notarize(id, type).then(res => {
createMessage.success(res.msg);
reload();
});
}
function handleSubmit(values) {
state.keyword = values?.keyword || '';
handleSearch();
}
function handleReset() {
state.keyword = '';
handleSearch();
}
function handleSearch() {
nextTick(() => reload({ page: 1 }));
}
// 新增委托/代理
function addOrUpdateHandle(id = '') {
openFormModal(true, { id, type: state.activeKey === 1 ? 0 : 1 });
}
// 删除委托
function handleDelete(id) {
del(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
function getUserStatusContent(status) {
return status === 0 ? '待确认' : status === 1 ? '已接受' : '已拒绝';
}
function getStatusContent(status) {
return status === 0 ? '未生效' : status === 1 ? '生效中' : '已失效';
}
function getStatusColor(status) {
return status === 0 ? '' : status === 1 ? 'processing' : 'error';
}
function getConfirmStatusContent(status) {
return status === 0 ? '待确认' : status === 1 ? '已接受' : '已拒绝';
}
function getConfirmStatusColor(status) {
return status === 0 ? '' : status === 1 ? 'success' : 'error';
}
onMounted(() => {
const route = useRoute();
if (route.query.config) {
state.activeKey = Number(route.query.config) || 1;
nextTick(() => reload({ page: 1 }));
} else {
reload({ page: 1 });
}
});
</script>

View File

@@ -0,0 +1,108 @@
<template>
<yunzhupaas-group-title content="第三方服务绑定" class="mb-20px" />
<div class="socials-list-justAuth">
<div class="socials-item" v-for="(item, i) in list" :key="i">
<div class="socials-item-main">
<img :src="item.logo" class="item-img" />
<div class="item-txt">
<p class="item-name">{{ item.name }}</p>
<p class="item-desc">{{ item.describetion }}</p>
</div>
<div class="item-btn">
<a-button v-if="item.entity" @click="handleDel(item.entity.userId, item.entity.id)">解绑</a-button>
<a-button v-if="!item.entity" type="primary" @click="handleBind(item.enname)">绑定</a-button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { getSocialsUserList, deleteSocials, socialsBind } from '@/api/permission/socialsUser';
import { reactive, toRefs, onMounted, onUnmounted } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
interface State {
list: any[];
listenerLoad: boolean;
}
let winUrl: any = '';
const { createMessage, createConfirm } = useMessage();
const state = reactive<State>({
list: [],
listenerLoad: false,
});
const { list } = toRefs(state);
const messageKey = 'callback';
function initData() {
state.list = [];
getSocialsUserList().then(res => {
state.list = res.data;
});
}
function handleDel(userId, id) {
createConfirm({
iconType: 'warning',
title: '提示',
content: '确定要解除该账号绑定?',
onOk: () => {
deleteSocials(userId, id)
.then(res => {
createMessage.success(res.msg).then(() => {
initData();
});
})
.catch(() => {
initData();
});
},
});
}
function handleBind(name) {
bindListener();
socialsBind(name).then(res => {
if (winUrl && !winUrl.closed) {
winUrl.location.replace(res.msg);
winUrl.focus();
return;
}
const iWidth = 750;
const iHeight = 500;
const iLeft = (window.screen.width - iWidth) / 2;
const iTop = (window.screen.height - iHeight) / 2;
winUrl = window.open(
res.msg,
'_blank',
`height=${iHeight},innerHeight=${iHeight},width=${iWidth},innerWidth=${iWidth},top=${iTop},left=${iLeft},toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no`,
);
});
}
function bindListener() {
if (!state.listenerLoad) {
window.addEventListener('message', e => {
const res = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
if (res.code == '200') {
createMessage.success({ content: res.message, key: messageKey }).then(() => {
initData();
window.removeEventListener('message', () => {});
});
}
if (res.code == '201') {
createMessage.error({ content: res.message, key: messageKey }).then(() => {
initData();
window.removeEventListener('message', () => {});
});
}
});
}
state.listenerLoad = true;
}
onMounted(() => {
initData();
});
onUnmounted(() => {
window.removeEventListener('message', () => {});
});
</script>

View File

@@ -0,0 +1,155 @@
<template>
<div class="password">
<yunzhupaas-group-title content="修改密码" class="mb-20px" />
<a-row>
<a-col :span="12">
<a-form :colon="false" :labelCol="{ style: { width: '100px' } }" :model="dataForm" :rules="dataRule" ref="formElRef">
<a-form-item label="旧密码" name="oldPassword">
<a-input-password v-model:value="dataForm.oldPassword" placeholder="请输入" />
</a-form-item>
<a-form-item label="新密码" name="password">
<a-input-password v-model:value="dataForm.password" placeholder="请输入" />
</a-form-item>
<a-form-item label="重复密码" name="password2">
<a-input-password v-model:value="dataForm.password2" placeholder="请输入" />
</a-form-item>
<a-form-item label="验证码" name="code">
<a-row>
<a-col :span="17">
<a-input v-model:value="dataForm.code" placeholder="请输入" />
</a-col>
<a-col :span="6" :offset="1" style="height: 32px">
<img alt="点击切换验证码" title="点击切换验证码" :src="apiUrl + codeImg" @click="initCodeImg" class="cursor-pointer" />
</a-col>
</a-row>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" @click="handleSubmit">保存</a-button>
</a-form-item>
</a-form>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, ref, onMounted, computed, unref } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { updatePassword } from '@/api/permission/userSetting';
import { useGlobSetting } from '@/hooks/setting';
import type { FormInstance } from 'ant-design-vue';
import { encryptByMd5 } from '@/utils/cipher';
import { useUserStore } from '@/store/modules/user';
import { useAppStore } from '@/store/modules/app';
interface State {
dataForm: any;
codeImg: string;
timestamp: number;
dataRule: any;
}
const userStore = useUserStore();
const validatePass = (_rule, value) => {
//是否包含数字
const containsNumbers = /[0-9]+/;
//是否包含小写字符
const includeLowercaseLetters = /[a-z]+/;
//是否包含大写字符
const includeUppercaseLetters = /[A-Z]+/;
//是否包含字符
const containsCharacters = /\W/;
//是否包含下划线
const includeUnderline = /_/;
if (!value) return Promise.reject('新密码不能为空');
if (unref(getSysConfig)?.passwordStrengthLimit == 1) {
if (unref(getSysConfig)?.passwordLengthMin && value.length < unref(getSysConfig)?.passwordLengthMinNumber)
return Promise.reject('新密码长度不能小于' + unref(getSysConfig)?.passwordLengthMinNumber + '位');
if (unref(getSysConfig)?.containsNumbers && !containsNumbers.test(value)) return Promise.reject('新密码必须包含数字');
if (unref(getSysConfig)?.includeLowercaseLetters && !includeLowercaseLetters.test(value)) return Promise.reject('新密码必须包含小写字母');
if (unref(getSysConfig)?.includeUppercaseLetters && !includeUppercaseLetters.test(value)) return Promise.reject('新密码必须包含大写字母');
if (unref(getSysConfig)?.containsCharacters && !containsCharacters.test(value) && !includeUnderline.test(value))
return Promise.reject('新密码必须包含字符');
return Promise.resolve();
} else {
return Promise.resolve();
}
};
var validatePass2 = (_rule, value) => {
if (value !== state.dataForm.password) {
return Promise.reject('两次密码输入不一致');
} else {
return Promise.resolve();
}
};
const state = reactive<State>({
dataForm: {
id: '',
account: '',
oldPassword: '',
password: '',
password2: '',
code: '',
},
codeImg: '',
timestamp: 0,
dataRule: {
oldPassword: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
password: [{ required: true, validator: validatePass, trigger: 'blur' }],
password2: [
{ required: true, message: '重复密码不能为空', trigger: 'blur' },
{ validator: validatePass2, trigger: 'blur' },
],
code: [{ required: true, message: '验证码不能为空', trigger: 'blur' }],
},
});
const { dataForm, codeImg, dataRule } = toRefs(state);
const formElRef = ref<FormInstance>();
const { createMessage } = useMessage();
const appStore = useAppStore();
const globSetting = useGlobSetting();
const apiUrl = ref(globSetting.apiUrl);
const getSysConfig: any = computed(() => appStore.getSysConfigInfo);
function init() {
resetForm();
initCodeImg();
}
function initCodeImg() {
state.timestamp = Math.random();
state.codeImg = `/api/file/ImageCode/${state.timestamp}`;
}
function resetForm() {
state.dataForm = {
oldPassword: '',
password: '',
password2: '',
code: '',
};
}
async function handleSubmit() {
try {
const values = await formElRef.value?.validate();
if (!values) return;
let query = {
oldPassword: encryptByMd5(state.dataForm.oldPassword),
password: encryptByMd5(state.dataForm.password),
code: state.dataForm.code,
timestamp: state.timestamp,
};
updatePassword(query)
.then(res => {
createMessage.success(res.msg).then(() => {
userStore.resetToken();
});
})
.catch(() => {});
} catch (_) {}
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,175 @@
<template>
<div class="yunzhupaas-content-wrapper bg-white sysLog mt-10px">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box">
<BasicForm class="search-form" @register="registerForm" @submit="handleSubmit" @reset="handleReset" />
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<BasicTable @register="registerLoginTable" :columns="loginTableColumns" :searchInfo="getSearchInfo">
<template #tableTitle>
<a-button type="error" preIcon="icon-ym icon-ym-btn-clearn" @click="handleDelete">{{ t('common.delText') }}</a-button>
<a-button type="link" danger @click="handleDelAll">一键清空</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'loginType'">
<div class="login-type-box">
<span class="circle-box" :class="record.loginType == 0 ? 'circle-box-primary' : 'circle-box-error'" />
{{ record.loginType == 0 ? '登录' : '退出' }}
</div>
</template>
<template v-if="column.key === 'loginMark'">
<a-tag :color="record.loginMark == 1 ? 'success' : 'error'">{{ record.loginMark == 1 ? '成功' : '失败' }}</a-tag>
</template>
</template>
</BasicTable>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, computed, nextTick } from 'vue';
import { getLogList } from '@/api/permission/userSetting';
import { delLog, batchDelLoginLog } from '@/api/system/log';
import { BasicForm, useForm } from '@/components/Form';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicTable, useTable, BasicColumn } from '@/components/Table';
import dayjs from 'dayjs';
import { useMessage } from '@/hooks/web/useMessage';
interface State {
searchInfo: any;
}
const state = reactive<State>({
searchInfo: {},
});
const { t } = useI18n();
const { createMessage, createConfirm } = useMessage();
const getSearchInfo = computed(() => ({ category: 1, ...state.searchInfo }));
const [registerForm] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
field: 'loginType',
label: '类型',
component: 'Select',
componentProps: {
options: [
{ fullName: '登录', id: 0 },
{ fullName: '退出', id: 1 },
],
},
},
{
field: 'loginMark',
label: '状态',
component: 'Select',
componentProps: {
placeholder: '请选择',
options: [
{ fullName: '成功', id: 1 },
{ fullName: '失败', id: 0 },
],
},
},
{
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: ['开始时间', '结束时间'],
},
},
],
fieldMapToTime: [['pickerVal', ['startTime', 'endTime']]],
});
const loginTableColumns: BasicColumn[] = [
{ title: '类型', dataIndex: 'loginType', width: 100 },
{ title: '时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '用户', dataIndex: 'userName', width: 120 },
{ title: 'IP地址', dataIndex: 'ipAddress', width: 120 },
{ title: '地点', dataIndex: 'ipAddressName', width: 120 },
{ title: '浏览器', dataIndex: 'browser', width: 120 },
{ title: '操作系统', dataIndex: 'platForm', width: 120 },
{ title: '耗时(毫秒)', dataIndex: 'requestDuration', width: 90, align: 'center' },
{ title: '状态', dataIndex: 'loginMark', width: 70, align: 'center' },
{ title: '操作说明', dataIndex: 'abstracts', width: 120 },
];
const [registerLoginTable, { reload, getSelectRows }] = useTable({
api: getLogList,
rowSelection: { type: 'checkbox' },
immediate: false,
clickToRowSelect: false,
clearSelectOnPageChange: true,
showTableSetting: false,
});
function handleSubmit(data) {
let obj = {};
for (let [key, value] of Object.entries(data)) {
if (value || value == 0) {
if (Array.isArray(value)) {
if (value.length) obj[key] = value;
} else {
obj[key] = value;
}
}
}
state.searchInfo = obj;
nextTick(() => reload());
}
function handleReset() {
state.searchInfo = {};
nextTick(() => reload());
}
function handleDelete() {
const list: any[] = getSelectRows() || [];
if (!list.length) return createMessage.error(t('common.selectDataTip'));
const query = {
ids: list.map(item => item.id),
};
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: t('common.batchDelTip'),
onOk: () => {
delLog(query).then(res => {
createMessage.success(res.msg);
reload();
});
},
});
}
function handleDelAll() {
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '此操作会将所有日志删除,是否继续?',
onOk: () => {
batchDelLoginLog().then(res => {
createMessage.success(res.msg);
reload();
});
},
});
}
onMounted(() => {
reload();
});
</script>

View File

@@ -0,0 +1,92 @@
<template>
<ScrollContainer>
<a-form :colon="false" :model="tenantInfo" ref="formElRef" :labelCol="{ style: { width: '100px' } }">
<a-row>
<a-col :span="24">
<yunzhupaas-group-title content="租户信息" class="mb-20px" />
</a-col>
<a-col :span="12">
<a-form-item label="租户名称">
<p>{{ tenantInfo.tenantName }}</p>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="租户号">
<p>{{ tenantInfo.tenantId }}</p>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="有效期">
<p>{{ tenantInfo.validTime }}</p>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="域名">
<p>{{ tenantInfo.domain }}</p>
</a-form-item>
</a-col>
<a-col :span="24">
<yunzhupaas-group-title content="单位信息" class="mb-20px" />
</a-col>
<a-col :span="12">
<a-form-item label="单位简称">
<p>{{ tenantInfo.unitInfoJson.unitShortName }}</p>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="信用代码">
<p>{{ tenantInfo.unitInfoJson.unitCreditCode }}</p>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="单位性质">
<p>{{ getUnitNature(tenantInfo.unitInfoJson.unitNature) }}</p>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="详细地址">
<p>{{ tenantInfo.unitInfoJson.unitAddress }}</p>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="单位简介">
<p>{{ tenantInfo.unitInfoJson.unitDescription }}</p>
</a-form-item>
</a-col>
<a-col :span="24">
<yunzhupaas-group-title content="联系人信息" class="mb-20px" />
</a-col>
<a-col :span="12">
<a-form-item label="联系人">
<p>{{ tenantInfo.userInfoJson.contacts }}</p>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话">
<p>{{ tenantInfo.userInfoJson.contactPhone }}</p>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系邮箱">
<p>{{ tenantInfo.userInfoJson.contactEmail }}</p>
</a-form-item>
</a-col>
</a-row>
</a-form>
</ScrollContainer>
</template>
<script setup lang="ts">
import { ScrollContainer } from '@/components/Container';
defineProps({
tenantInfo: { type: Object, default: () => ({}) },
});
function getUnitNature(val) {
if (val == 0) return '个体户';
if (val == 1) return '合伙企业';
if (val == 2) return '集体企业';
if (val == 3) return '私营企业';
if (val == 4) return '国有企业';
}
</script>

View File

@@ -0,0 +1,481 @@
<template>
<a-tabs v-model:activeKey="activeKey" class="userInfo-tabs">
<a-tab-pane key="1" tab="账户信息">
<a-row>
<a-col :span="12">
<a-form :colon="false" labelAlign="right" :labelCol="{ style: { width: '100px' } }" class="pt-10px">
<a-form-item label="账户">
<a-input v-model:value="form.account" readonly />
</a-form-item>
<a-form-item label="所属组织">
<a-input v-model:value="form.organize" readonly />
</a-form-item>
<a-form-item label="直属主管">
<a-input v-model:value="form.manager" readonly />
</a-form-item>
<a-form-item label="岗位">
<a-input v-model:value="form.position" readonly />
</a-form-item>
<a-form-item label="职级">
<a-input v-model:value="form.ranks" readonly />
</a-form-item>
<a-form-item label="角色">
<a-input v-model:value="form.roleId" readonly />
</a-form-item>
<a-form-item label="注册时间">
<a-input v-model:value="getCreatorTime" readonly />
</a-form-item>
<a-form-item label="上次登录">
<a-input v-model:value="getPrevLogTime" readonly />
</a-form-item>
<a-form-item label="入职日期">
<a-input v-model:value="getEntryDate" readonly />
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane key="2" tab="个人资料">
<a-form
:colon="false"
labelAlign="right"
:model="form2"
:rules="state.form2Rule"
ref="form2ElRef"
:labelCol="{ style: { width: '100px' } }"
class="pt-10px">
<a-row>
<a-col :span="12">
<a-form-item label="姓名" name="realName">
<a-input v-model:value="form2.realName" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="性别">
<yunzhupaas-select v-model:value="form2.gender" :options="genderOptions" placeholder="请选择" :fieldNames="{ value: 'enCode' }" showSearch />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="民族">
<yunzhupaas-select v-model:value="form2.nation" :options="nationOptions" placeholder="请选择" showSearch />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="籍贯">
<a-input v-model:value="form2.nativePlace" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="证件类型">
<yunzhupaas-select v-model:value="form2.certificatesType" :options="certificatesTypeOptions" placeholder="请选择" showSearch />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="证件号码">
<a-input v-model:value="form2.certificatesNumber" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="文化程度">
<yunzhupaas-select v-model:value="form2.education" :options="educationOptions" placeholder="请选择" showSearch />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="出生年月">
<yunzhupaas-date-picker v-model:value="form2.birthday" placeholder="请选择" format="YYYY-MM-DD" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="办公电话">
<a-input v-model:value="form2.telePhone" :maxlength="20" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="办公座机">
<a-input v-model:value="form2.landline" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="手机号码">
<a-input v-model:value="form2.mobilePhone" :maxlength="20" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="电子邮箱">
<a-input v-model:value="form2.email" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="紧急联系">
<a-input v-model:value="form2.urgentContacts" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="紧急电话">
<a-input v-model:value="form2.urgentTelePhone" :maxlength="50" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="通讯地址">
<a-input v-model:value="form2.postalAddress" :maxlength="300" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="自我介绍">
<yunzhupaas-textarea v-model:value="form2.signature" :maxlength="300" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label=" ">
<a-button type="primary" @click="handleSubmit">保存</a-button>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-tab-pane>
<a-tab-pane key="3" tab="个人签名">
<a-row class="sign-list" :gutter="40">
<a-col :span="6" class="sign-item add-sign">
<div class="sign-item-main">
<a-dropdown trigger="click">
<i class="add-icon icon-ym icon-ym-btn-add" @click.prevent></i>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="openSignModal">在线签名</a-menu-item>
<a-menu-item key="3">
<a-upload :showUploadList="false" accept="image/*" :before-upload="beforeUpload">
<div>图片上传</div>
</a-upload>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</a-col>
<a-col :span="6" class="sign-item" :key="i" v-for="(item, i) in state.signList">
<div :class="item.isDefault ? 'sign-item-main active' : 'sign-item-main'">
<img :src="item.signImg" alt="" class="sign-img" />
<div class="icon-checked" v-if="item.isDefault">
<check-outlined />
</div>
<div v-if="!item.isDefault" class="add-button">
<a-button size="small" @click="delSign(item.id)" class="mr-10px">删除</a-button>
<a-button size="small" type="primary" @click="updateDefault(item.id, item.signImg)">设为默认</a-button>
</div>
</div>
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane key="4" tab="审批常用语" class="!p-0px">
<BasicTable @register="registerTable">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ t('common.addText') }}</a-button>
</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)" />
</template>
</template>
</BasicTable>
</a-tab-pane>
</a-tabs>
<SignModal ref="signModalRef" submitOnConfirm @confirm="getSign" />
<Form @register="registerForm" @reload="reload" />
</template>
<script setup lang="ts">
import { updateUserInfo, getSignList, deleteSign, updateDefaultSign, createSign } from '@/api/permission/userSetting';
import { reactive, toRefs, ref, computed, onMounted, unref } from 'vue';
import { useBaseStore } from '@/store/modules/base';
import { useUserStore } from '@/store/modules/user';
import { useMessage } from '@/hooks/web/useMessage';
import type { FormInstance } from 'ant-design-vue';
import { formatToDateTime } from '@/utils/dateUtil';
import SignModal from '@/components/Yunzhupaas/Sign/src/SignModal.vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import { getBase64WithFile } from '@/components/Yunzhupaas/Upload/src/helper';
import { getCommonWordsList, delCommonWords } from '@/api/system/commonWords';
import { BasicTable, useTable, TableAction, BasicColumn, ActionItem } from '@/components/Table';
import { useModal } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import Form from '@/views/system/commonWords/Form.vue';
interface State {
activeKey: string;
educationOptions: any[];
certificatesTypeOptions: any[];
genderOptions: any[];
nationOptions: any[];
signList: any[];
form: any;
form2: any;
form2Rule: any;
}
const props = defineProps({
user: { type: Object, default: () => ({}) },
});
const emit = defineEmits(['updateInfo']);
const baseStore = useBaseStore();
const userStore = useUserStore();
const { createMessage } = useMessage();
const form2ElRef = ref<FormInstance>();
const signModalRef = ref(null);
const { t } = useI18n();
const state = reactive<State>({
activeKey: '1',
educationOptions: [],
certificatesTypeOptions: [],
genderOptions: [],
nationOptions: [],
signList: [],
form: {},
form2: {
realName: '',
signature: '',
gender: 1,
nation: '',
nativePlace: '',
certificatesType: '',
certificatesNumber: '',
education: '',
birthday: null,
telePhone: '',
landline: '',
mobilePhone: '',
email: '',
urgentContacts: '',
urgentTelePhone: '',
postalAddress: '',
},
form2Rule: {
realName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
},
});
const { activeKey, form, form2, educationOptions, certificatesTypeOptions, genderOptions, nationOptions } = toRefs(state);
const columns: BasicColumn[] = [
{ title: '常用语', dataIndex: 'commonWordsText' },
{ title: '使用次数', dataIndex: 'usesNum', width: 80, align: 'center' },
{ title: '状态', dataIndex: 'enabledMark', width: 80, align: 'center' },
];
const [registerForm, { openModal: openFormModal }] = useModal();
const [registerTable, { reload }] = useTable({
api: getCommonWordsList,
searchInfo: { commonWordsType: 1 },
columns,
actionColumn: {
width: 100,
title: '操作',
dataIndex: 'action',
},
});
const getCreatorTime = computed(() => (state.form.creatorTime ? formatToDateTime(state.form.creatorTime, 'YYYY-MM-DD HH:mm:ss') : ''));
const getEntryDate = computed(() => (state.form.entryDate ? formatToDateTime(state.form.entryDate, 'YYYY-MM-DD HH:mm:ss') : ''));
const getPrevLogTime = computed(() => (state.form.prevLogTime ? formatToDateTime(state.form.prevLogTime, 'YYYY-MM-DD HH:mm:ss') : ''));
async function getOptions() {
const educationRes = (await baseStore.getDictionaryData('Education')) as any;
state.educationOptions = educationRes;
const certificateTypeRes = (await baseStore.getDictionaryData('certificateType')) as any;
state.certificatesTypeOptions = certificateTypeRes;
const sexRes = (await baseStore.getDictionaryData('sex')) as any;
state.genderOptions = sexRes;
const nationRes = (await baseStore.getDictionaryData('Nation')) as any;
state.nationOptions = nationRes;
}
function getInfo() {
state.form = props.user;
for (let key of Object.keys(state.form2)) {
state.form2[key] = state.form[key];
}
}
function getSign() {
getSignList().then(res => {
state.signList = res.data || [];
});
}
async function handleSubmit() {
try {
const values = await form2ElRef.value?.validate();
if (!values) return;
updateUserInfo(state.form2).then(res => {
createMessage.success(res.msg);
emit('updateInfo');
userStore.setUserInfo({ userName: state.form2.realName });
});
} catch (_) {}
}
function openSignModal() {
const signRef = unref(signModalRef) as any;
signRef?.openModal();
}
function beforeUpload(file: File) {
const isAccept = new RegExp('image/*').test(file.type);
if (!isAccept) {
createMessage.error(`请上传图片`);
return;
}
if (file.size / 1024 > 500) {
createMessage.error('操作失败图片大小超出500K');
return;
}
getBase64WithFile(file).then(({ result: thumbUrl }) => {
addSign(thumbUrl);
});
return false;
}
function addSign(signImg) {
const query = {
signImg: signImg,
isDefault: 0,
};
createSign(query).then(res => {
createMessage.success(res.msg);
getSign();
});
}
function updateDefault(id, signImg) {
updateDefaultSign(id)
.then(res => {
createMessage.success(res.msg);
userStore.setUserInfo({ signImg: signImg });
getSign();
})
.catch(_ => {
getSign();
});
}
function delSign(id) {
deleteSign(id).then(res => {
createMessage.success(res.msg);
getSign();
});
}
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 addOrUpdateHandle(id = '') {
openFormModal(true, { id, commonWordsType: 1 });
}
function handleDelete(id) {
delCommonWords(id).then(res => {
createMessage.success(res.msg);
reload();
});
}
onMounted(() => {
getOptions();
getInfo();
getSign();
});
</script>
<style lang="less">
.userInfo-tabs {
.sign-list {
.sign-item .sign-item-main .icon-checked {
border: 16px solid @primary-color;
border-left: 16px solid transparent !important;
border-top: 16px solid transparent !important;
}
}
.ant-tabs-nav {
margin: 0;
}
}
</style>
<style lang="less" scoped>
html[data-theme='dark'] {
.sign-list .sign-item .sign-item-main {
background-color: #fff;
}
}
.userInfo-tabs {
height: 100%;
.ant-tabs-tabpane {
padding: 10px;
overflow-x: hidden;
}
}
:deep(.ant-tabs-content-holder) {
height: calc(100% - 64px);
overflow: auto;
}
.sign-list {
padding: 20px 50px 0;
.sign-item {
margin-bottom: 20px;
.sign-item-main {
position: relative;
height: 160px;
background-color: @app-content-background;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
overflow: hidden;
.icon-checked {
display: block;
width: 16px;
height: 16px;
border-bottom-right-radius: 10px;
position: absolute;
right: -1px;
bottom: -1px;
.anticon-check {
position: absolute;
top: -1px;
left: -1px;
font-size: 14px;
color: #fff;
}
}
&.active {
border: 1px solid @primary-color;
box-shadow: 0 0 6px rgba(6, 58, 108, 0.26);
color: @primary-color;
}
&:hover {
.add-button {
display: flex;
width: 100%;
height: 100%;
border-radius: 10px;
background-color: rgba(157, 158, 159, 0.8);
justify-content: center;
align-items: center;
}
}
.add-button {
position: absolute;
display: none;
}
.add-icon {
font-size: 50px;
color: @text-color-secondary;
}
.sign-img {
width: 100%;
height: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,453 @@
<template>
<div class="yunzhupaas-content-wrapper profile-wrapper bg-white">
<a-tabs v-model:activeKey="activeKey" tab-position="left" class="common-left-tabs profile-left-tabs" destroyInactiveTabPane>
<a-tab-pane key="user" tab="个人资料">
<UserInfo :user="user" @updateInfo="getInfo" />
</a-tab-pane>
<a-tab-pane key="tenantInfo" tab="租户信息" v-if="isTenant">
<TenantInfo :tenantInfo="tenantInfo" />
</a-tab-pane>
<a-tab-pane key="password" tab="修改密码">
<Password />
</a-tab-pane>
<a-tab-pane key="line" disabled></a-tab-pane>
<a-tab-pane key="organize" tab="我的组织">
<yunzhupaas-group-title content="我的组织" helpMessage="用户可以自行切换组织信息,我的组织默认只能进行单选" />
<div class="organize-list">
<a-row :gutter="80" v-if="state.organizeList.length">
<a-col :span="12" class="organize-item" v-for="(item, i) in state.organizeList" :key="i">
<div class="organize-item-main" :class="{ active: state.activeOrganize === item.id }" @click="changeMajor(item.id, 'Organize')">
<i class="icon-ym icon-ym-organization"></i>
<p class="organize-name">{{ item.fullName }}</p>
<p class="btn">默认</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-col>
</a-row>
<yunzhupaas-empty v-else />
</div>
</a-tab-pane>
<a-tab-pane key="position" tab="我的岗位">
<yunzhupaas-group-title content="我的岗位" helpMessage="用户可以自行切换我的组织内的岗位信息,我的岗位默认只能进行单选" />
<div class="organize-list">
<a-row :gutter="80" v-if="state.positionList.length">
<a-col :span="12" class="organize-item" v-for="(item, i) in state.positionList" :key="i">
<div class="organize-item-main" :class="{ active: state.activePosition === item.id }" @click="changeMajor(item.id, 'Position')">
<i class="icon-ym icon-ym-wf-outgoingApply"></i>
<p class="organize-name">{{ item.fullName }}</p>
<p class="btn">主岗</p>
<div class="icon-checked">
<check-outlined />
</div>
</div>
</a-col>
</a-row>
<yunzhupaas-empty v-else />
</div>
</a-tab-pane>
<a-tab-pane key="subordinate" tab="我的下属">
<yunzhupaas-group-title content="我的下属" />
<div class="subordinate-list">
<BasicTree ref="subTreeRef" :treeData="state.subordinateList" :load-data="loadData">
<template #title="item">
<a-card class="subordinate-tree-node" shadow="never" slot-scope="{ data }">
<a-avatar :size="50" :src="apiUrl + item.avatar"></a-avatar>
<div class="text">
<p>{{ item.userName }}</p>
<p class="user-text">{{ item.department }}{{ item.position ? '/' + item.position : '' }}</p>
</div>
</a-card>
</template>
</BasicTree>
</div>
</a-tab-pane>
<a-tab-pane key="entrust" tab="委托代理">
<Entrust />
</a-tab-pane>
<a-tab-pane key="justAuth" tab="绑定设置" v-if="getUseSocials">
<JustAuth />
</a-tab-pane>
<a-tab-pane key="authorize" tab="系统权限">
<Authorize />
</a-tab-pane>
<a-tab-pane key="sysLog" tab="登录日志">
<SysLog />
</a-tab-pane>
<template #leftExtra>
<div class="head">
<a-upload
:showUploadList="false"
:action="uploadUrl + '/userAvatar'"
class="avatar-uploader"
:headers="getHeaders"
accept="image/*"
:before-upload="beforeUpload"
@change="handleChange">
<div class="avatar-box">
<a-avatar :size="50" :src="apiUrl + user.avatar" class="avatar" v-if="user.avatar" />
<div class="avatar-hover">更换头像</div>
</div>
</a-upload>
<span class="username">{{ user.realName }}</span>
</div>
</template>
</a-tabs>
</div>
</template>
<script lang="ts" setup>
import { getUserSettingInfo, getSubordinate, updateAvatar, getUserOrganizes, getUserPositions, setMajor } from '@/api/permission/userSetting';
import { reactive, toRefs, ref, computed, onMounted, watch, unref } from 'vue';
import { useUserStore } from '@/store/modules/user';
import { useGlobSetting } from '@/hooks/setting';
import { useMessage } from '@/hooks/web/useMessage';
import { getToken } from '@/utils/auth';
import type { UploadChangeParam } from 'ant-design-vue';
import { createLocalStorage } from '@/utils/cache';
import { CheckOutlined } from '@ant-design/icons-vue';
import { BasicTree, TreeActionType } from '@/components/Tree';
import UserInfo from './components/UserInfo.vue';
import TenantInfo from './components/TenantInfo.vue';
import Password from './components/Password.vue';
import JustAuth from './components/JustAuth.vue';
import Authorize from './components/Authorize.vue';
import SysLog from './components/SysLog.vue';
import Entrust from './components/Entrust/index.vue';
import { useRoute } from 'vue-router';
interface State {
activeKey: string;
user: any;
tenantInfo: any;
isTenant: boolean;
userLoading: boolean;
loading: boolean;
nodeId: string;
subordinateList: any[];
organizeList: any[];
positionList: any[];
activeOrganize: string;
activePosition: string;
}
const { createMessage } = useMessage();
const ls = createLocalStorage();
const userStore = useUserStore();
const globSetting = useGlobSetting();
const apiUrl = ref(globSetting.apiUrl);
const uploadUrl = ref(globSetting.uploadUrl);
const subTreeRef = ref<Nullable<TreeActionType>>(null);
const state = reactive<State>({
activeKey: '',
user: {},
tenantInfo: {},
isTenant: false,
userLoading: false,
loading: false,
nodeId: '0',
subordinateList: [],
organizeList: [],
positionList: [],
activeOrganize: '',
activePosition: '',
});
const { activeKey, user, tenantInfo, isTenant } = toRefs(state);
const route = useRoute();
const getHeaders = computed(() => ({ Authorization: getToken() as string }));
const getUseSocials = computed(() => !!ls.get('useSocials'));
watch(
() => state.activeKey,
val => {
if (val === 'subordinate') {
state.nodeId = '0';
getSubordinateList();
return;
}
if (val === 'organize') return getUserOrganizesList();
if (val === 'position') return getUserPositionsList();
},
);
function beforeUpload(file) {
let isAccept = new RegExp('image/*').test(file.type);
if (!isAccept) createMessage.error(`请上传图片`);
return isAccept;
}
function handleChange({ file }: UploadChangeParam) {
if (file.status === 'error') {
createMessage.error('上传失败');
return;
}
if (file.status === 'done') {
if (file.response.code === 200) {
if (!file.response.data || !file.response.data.name) return;
updateAvatar(file.response.data.name).then(res => {
state.user.avatar = file.response.data.url;
userStore.setUserInfo({ headIcon: file.response.data.url });
createMessage.success(res.msg);
});
} else {
createMessage.error(file.response.msg);
}
}
}
function getInfo() {
state.userLoading = true;
getUserSettingInfo().then(res => {
state.user = res.data;
state.tenantInfo = res.data.currentTenantInfo;
state.isTenant = res.data.isTenant || false;
if (!route.query.config) state.activeKey = 'user';
state.userLoading = false;
});
}
function getSubordinateList() {
state.loading = true;
getSubordinate(state.nodeId).then(res => {
state.subordinateList = res.data;
state.loading = false;
});
}
function loadData(node) {
state.nodeId = node.id;
return new Promise((resolve: (value?: unknown) => void) => {
getSubordinate(state.nodeId).then(res => {
const list = res.data;
getTree().updateNodeByKey(node.eventKey, { children: list, isLeaf: !list.length });
resolve();
});
});
}
function getTree() {
const tree = unref(subTreeRef);
if (!tree) {
throw new Error('tree is null!');
}
return tree;
}
function getUserOrganizesList() {
getUserOrganizes().then(res => {
state.organizeList = res.data || [];
const list = state.organizeList.filter(o => o.isDefault);
if (!list.length) return (state.activeOrganize = '');
const activeItem = list[0];
state.activeOrganize = activeItem.id;
});
}
function getUserPositionsList() {
getUserPositions().then(res => {
state.positionList = res.data || [];
const list = state.positionList.filter(o => o.isDefault);
if (!list.length) return (state.activePosition = '');
const activeItem = list[0];
state.activePosition = activeItem.id;
});
}
function changeMajor(majorId, majorType) {
if (state['active' + majorType] === majorId) return;
const query = { majorId, majorType };
setMajor(query).then(res => {
state['active' + majorType] = majorId;
createMessage.success(res.msg).then(() => {
location.reload();
});
});
}
onMounted(() => {
if (route.query.config) state.activeKey = 'entrust';
getInfo();
});
</script>
<style lang="less">
.profile-wrapper {
.profile-left-tabs {
width: 100%;
margin-right: 0;
.ant-tabs-tab-disabled {
padding: 0 !important;
.ant-tabs-tab-btn {
border-bottom: 1px solid @border-color-base1;
width: 100%;
}
}
.ant-tabs-content-holder {
width: 100% !important;
.ant-tabs-content-left {
height: 100%;
& > .ant-tabs-tabpane {
padding-left: 10px;
height: 100%;
}
}
}
}
.head {
height: 70px;
width: 160px;
padding-top: 10px;
padding-left: 10px;
.avatar-uploader {
display: inline-block;
vertical-align: top;
.avatar-hover {
position: absolute;
left: 0;
top: 0;
font-size: 12px;
display: none;
overflow: hidden;
width: 50px;
height: 50px;
text-align: center;
border-radius: 50%;
line-height: 50px;
color: #fff;
cursor: pointer;
background: rgba(0, 0, 0, 0.5);
}
&:hover {
& .avatar-hover {
display: block;
}
}
}
.avatar-box {
position: relative;
}
.avatar {
display: inline-block;
width: 50px;
height: 50px;
overflow: hidden;
border-radius: 50%;
vertical-align: top;
margin-right: 10px;
}
.username {
line-height: 50px;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 90px;
display: inline-block;
}
}
.organize-list {
width: 100%;
padding: 50px;
.organize-item {
margin-bottom: 30px;
.organize-item-main {
height: 70px;
position: relative;
border-radius: 4px;
border: 1px solid #dcdfe6;
display: flex;
align-items: center;
padding: 0 20px;
cursor: pointer;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.16);
color: @text-color-base;
&.active {
border: 1px solid @primary-color;
box-shadow: 0 0 6px rgba(6, 58, 108, 0.26);
color: @primary-color;
.btn,
.icon-checked {
display: block;
}
}
.icon-ym {
font-size: 24px;
margin-right: 10px;
}
.organize-name {
line-height: 24px;
font-size: 14px;
}
.btn {
display: none;
position: absolute;
right: 45px;
bottom: 7px;
font-size: 12px;
}
.icon-checked {
display: none;
width: 20px;
height: 20px;
border: 20px solid @primary-color;
border-left: 20px solid transparent !important;
border-top: 20px solid transparent !important;
border-bottom-right-radius: 2px;
position: absolute;
transform: scale(0.9);
right: -2px;
bottom: -2px;
.anticon-check {
position: absolute;
top: 0;
left: 0;
font-size: 16px;
color: #fff;
}
}
}
}
}
.subordinate-list {
height: calc(100% - 50px);
padding-top: 20px;
overflow: auto;
.ant-tree {
.ant-tree-switcher {
line-height: 80px !important;
.ant-tree-switcher-icon,
.ant-tree-switcher-icon {
font-size: 18px;
}
}
.ant-tree-node-content-wrapper {
height: 80px !important;
}
.ant-tree-treenode {
background-color: transparent !important;
}
.subordinate-tree-node {
width: 300px;
.ant-card-body {
display: flex;
padding: 10px 10px;
align-items: center;
.ant-avatar {
margin-right: 10px;
flex-shrink: 0;
}
.text {
font-size: 14px;
width: calc(100% - 60px);
p {
line-height: 25px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
}
}
.user-text {
color: #999;
font-size: 12px;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
import { unref } from 'vue';
import { useRouter } from 'vue-router';
const { currentRoute, replace } = useRouter();
const { params, query } = unref(currentRoute);
const { path, _redirect_type = 'path' } = params;
Reflect.deleteProperty(params, '_redirect_type');
Reflect.deleteProperty(params, 'path');
const _path = Array.isArray(path) ? path.join('/') : path;
if (_redirect_type === 'name') {
replace({
name: _path,
query,
params: JSON.parse((params._origin_params as string) ?? '{}'),
});
} else {
replace({
path: _path.startsWith('/') ? _path : '/' + _path,
query,
});
}
</script>

View File

@@ -0,0 +1,61 @@
<template>
<div class="yunzhupaas-content-wrapper bg-white">
<iframe :src="state.url" width="100%" height="100%" frameborder="0" />
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted } from 'vue';
import { useGlobSetting } from '@/hooks/setting';
import { getToken } from '@/utils/auth';
import { useRoute } from 'vue-router';
import { getDataReportInfo } from '@/api/onlineDev/dataReport';
interface State {
url: string;
}
defineOptions({ name: 'dynamicDataReport' });
defineEmits(['register']);
const { report } = useGlobSetting();
const state = reactive<State>({
url: '',
});
function init() {
const route = useRoute();
const id = route.meta.relationId;
if (!id) return;
let targetUrl = `${report}/preview.html?id=${id}&token=${getToken()}&page=1&from=menu`;
getDataReportInfo(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);
}
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,127 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import {
getDictionaryDataTypeSelector,
getDictionaryDataInfo as getInfo,
createDictionaryData as create,
updateDictionaryData as update,
} from '@/api/systemData/dictionary';
import { ref, unref, computed } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { useBaseStore } from '@/store/modules/base';
import { useI18n } from '@/hooks/web/useI18n';
const emit = defineEmits(['register', 'reload']);
const [registerForm, { setFieldsValue, resetFields, validate, updateSchema }] = useForm({
schemas: [
{
field: 'parentId',
label: '项目上级',
defaultValue: '0',
component: 'TreeSelect',
componentProps: { placeholder: '请选择', showSearch: true },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'fullName',
label: '字典名称',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'enCode',
label: '字典编码',
component: 'Input',
componentProps: { placeholder: '请输入', maxlength: 50 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'sortCode',
label: '排序',
defaultValue: 0,
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
},
{
field: 'enabledMark',
label: '状态',
defaultValue: 1,
component: 'Switch',
},
{
field: 'description',
label: '说明',
component: 'Textarea',
componentProps: { placeholder: '请输入', rows: 3 },
},
],
});
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
const id = ref('');
const isTree = ref(0);
const typeId = ref('');
const treeData = ref([]);
const { createMessage } = useMessage();
const { t } = useI18n();
const baseStore = useBaseStore();
const getTitle = computed(() => (!unref(id) ? t('common.addText') : t('common.editText')));
function init(data) {
changeLoading(true);
resetFields();
id.value = data.id;
isTree.value = data.isTree;
typeId.value = data.typeId;
updateSchema({ field: 'parentId', componentProps: { disabled: !unref(isTree) } });
getDictionaryDataTypeSelector(data.typeId, data.isTree, data.id).then(res => {
treeData.value = res.data.list;
updateSchema([
{
field: 'parentId',
componentProps: { options: treeData.value },
},
]);
if (id.value) {
getInfo(id.value).then(res => {
setFieldsValue(res.data);
typeId.value = res.data.dictionaryTypeId;
changeLoading(false);
});
} else {
setFieldsValue({ parentId: res.data.list[0].id });
changeLoading(false);
}
});
}
async function handleSubmit() {
const values = await validate();
if (!values) return;
changeOkLoading(true);
const query = {
...values,
id: id.value,
dictionaryTypeId: typeId.value,
};
const formMethod = id.value ? update : create;
formMethod(query)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
baseStore.setDictionaryList();
closeModal();
emit('reload');
})
.catch(() => {
changeOkLoading(false);
});
}
</script>

View File

@@ -0,0 +1,116 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable" :searchInfo="searchInfo" :tableSetting="tableSetting">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ t('common.addText') }}</a-button>
</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)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form @register="registerForm" @reload="reload" />
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { getDictionaryDataList, delDictionaryData } from '@/api/systemData/dictionary';
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';
defineOptions({ name: 'dynamic-dictionary' });
const { createMessage } = useMessage();
const { t } = useI18n();
const baseStore = useBaseStore();
const route = useRoute();
const [registerForm, { openModal: openFormModal }] = useModal();
const columns: BasicColumn[] = [
{ title: '名称', dataIndex: 'fullName' },
{ title: '编码', dataIndex: 'enCode' },
{ title: '排序', dataIndex: 'sortCode', width: 70, align: 'center' },
{ title: '状态', dataIndex: 'enabledMark', width: 70, align: 'center' },
];
const searchInfo = reactive({
typeId: '',
isTree: 0,
});
const tableSetting = reactive({
expand: false,
});
const [registerTable, { reload }] = useTable({
api: getDictionaryDataList,
columns,
immediate: false,
pagination: false,
isTreeTable: true,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: { placeholder: t('common.enterKeyword'), submitOnPressEnter: true },
colProps: { span: 6 },
},
],
},
actionColumn: {
width: 100,
title: '操作',
dataIndex: 'action',
},
});
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 addOrUpdateHandle(id = '') {
openFormModal(true, {
id,
...searchInfo,
});
}
function handleDelete(id) {
delDictionaryData(id).then(res => {
createMessage.success(res.msg);
baseStore.setDictionaryList();
reload();
});
}
onMounted(() => {
const { meta } = route;
searchInfo.typeId = meta.relationId as string;
searchInfo.isTree = (meta.isTree as number) || 0;
tableSetting.expand = !!searchInfo.isTree;
searchInfo.typeId && reload();
reload();
});
</script>

View File

@@ -0,0 +1,144 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :show-back-icon="false" :show-cancel-btn="false" :title="config.fullName">
<template #insertToolbar>
<a-button type="primary" @click="handleSubmit" :loading="btnLoading">{{ getOkText }}</a-button>
<a-button type="warning" class="ml-10px" @click="handleReset">{{ t('common.resetText') }}</a-button>
</template>
<div class="p-10px" :style="{ margin: '0 auto', width: formConf.fullScreenWidth || '100%' }">
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</div>
</BasicPopup>
</template>
<script lang="ts" setup>
import { createModel } from '@/api/onlineDev/visualDev';
import { reactive, toRefs, nextTick, ref, unref, computed } from 'vue';
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
import { BasicPopup, usePopupInner } from '@/components/Popup';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import dayjs from 'dayjs';
import { getDateTimeUnit } from '@/utils/yunzhupaas';
interface State {
formConf: any;
config: any;
loading: boolean;
btnLoading: boolean;
key: number;
}
defineEmits(['register']);
const { createMessage } = useMessage();
const { t } = useI18n();
const userStore = useUserStore();
const [registerPopup, { changeLoading }] = usePopupInner(init);
const parserRef = ref<any>(null);
const state = reactive<State>({
formConf: {},
config: {},
loading: false,
btnLoading: false,
key: +new Date(),
});
const { formConf, key, loading, config, btnLoading } = toRefs(state);
const Parser = createAsyncComponent(() => import('@/components/FormGenerator/src/components/Parser.vue'));
const getOkText = computed(() => {
const text = state.formConf.confirmButtonTextI18nCode
? t(state.formConf.confirmButtonTextI18nCode, state.formConf.confirmButtonText)
: state.formConf.confirmButtonText;
return text || t('common.okText');
});
function fillFormData(form, data) {
const userInfo = userStore.getUserInfo;
const currDate = new Date();
const loop = list => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.__vModel__) {
if (item.__config__.defaultCurrent) {
if (item.__config__.yunzhupaasKey === 'datePicker') {
item.__config__.defaultValue = dayjs(currDate).startOf(getDateTimeUnit(item.format)).valueOf();
}
if (item.__config__.yunzhupaasKey === 'timePicker') {
item.__config__.defaultValue = dayjs(currDate).format(item.format || 'HH:mm:ss');
}
if (item.__config__.yunzhupaasKey === 'organizeSelect' && userInfo.organizeIdList?.length) {
item.__config__.defaultValue = item.multiple ? [userInfo.organizeIdList] : userInfo.organizeIdList;
}
if (item.__config__.yunzhupaasKey === 'depSelect' && userInfo.departmentId) {
item.__config__.defaultValue = item.multiple ? [userInfo.departmentId] : userInfo.departmentId;
}
if (item.__config__.yunzhupaasKey === 'userSelect' && userInfo.userId) {
item.__config__.defaultValue = item.multiple ? [userInfo.userId] : userInfo.userId;
}
if (item.__config__.yunzhupaasKey === 'usersSelect' && userInfo.userId) {
item.__config__.defaultValue = item.multiple ? [userInfo.userId + '--user'] : userInfo.userId + '--user';
}
if (item.__config__.yunzhupaasKey === 'posSelect' && userInfo.positionIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.positionIds.map(o => o.id) : userInfo.positionIds[0].id;
}
if (item.__config__.yunzhupaasKey === 'roleSelect' && userInfo.roleIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.roleIds : userInfo.roleIds[0];
}
if (item.__config__.yunzhupaasKey === 'groupSelect' && userInfo.groupIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.groupIds : userInfo.groupIds[0];
}
if (item.__config__.yunzhupaasKey === 'sign' && userInfo.signImg) {
item.__config__.defaultValue = userInfo.signImg;
}
}
}
if (item.__config__ && item.__config__.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children);
}
}
};
loop(form.fields);
form.formData = data;
}
function init(data) {
changeLoading(true);
state.loading = true;
state.config = data;
state.formConf = data.formData ? JSON.parse(data.formData) : {};
fillFormData(state.formConf, {});
nextTick(() => {
changeLoading(false);
state.loading = false;
state.key = +new Date();
});
}
function submitForm(data, callback) {
if (!data) return;
state.btnLoading = true;
const dataForm = { data: JSON.stringify(data) };
createModel(state.config.modelId, dataForm)
.then(res => {
createMessage.success(res.msg);
if (callback && typeof callback === 'function') callback();
state.btnLoading = false;
handleReset();
})
.catch(() => {
state.btnLoading = false;
});
}
function handleReset() {
fillFormData(state.formConf, {});
nextTick(() => {
getParser().handleReset();
});
}
function handleSubmit() {
if (state.config.isPreview) return createMessage.warning('功能预览不支持数据保存');
getParser().handleSubmit();
}
function getParser() {
const parser = unref(parserRef);
if (!parser) throw new Error('parser is null!');
return parser;
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div class="yunzhupaas-content-wrapper bg-white">
<FormPopup @register="registerFormPopup" />
<FlowParser @register="registerFlowParser" @reload="init()" />
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { usePopup } from '@/components/Popup';
import FormPopup from './FormPopup.vue';
import FlowParser from '@/views/workFlow/components/FlowParser.vue';
const props = defineProps(['config', 'modelId', 'isPreview']);
const [registerFormPopup, { openPopup: openFormPopup }] = usePopup();
const [registerFlowParser, { openPopup: openFlowParser }] = usePopup();
function openFlowPopup() {
const data = {
id: '',
flowId: props.config.flowId,
opType: '-1',
hideCancelBtn: true,
hideSaveBtn: true,
};
openFlowParser(true, data);
}
function init() {
if (props.config.enableFlow) return openFlowPopup();
const data = {
modelId: props.modelId,
isPreview: props.isPreview,
...props.config,
};
openFormPopup(true, data);
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,97 @@
<template>
<component :is="currentView" :config="config" :modelId="modelId" :isPreview="isPreview" :isDataManage="isDataManage" v-if="showPage" />
</template>
<script lang="ts" setup>
import { reactive, onMounted, toRefs, markRaw } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getConfigData } from '@/api/onlineDev/visualDev';
import { getFlowStartFormId } from '@/api/workFlow/template';
import { useMessage } from '@/hooks/web/useMessage';
import { useTabs } from '@/hooks/web/useTabs';
import { useBaseStore } from '@/store/modules/base';
import Form from './form/index.vue';
import List from './list/index.vue';
interface State {
currentView: any;
showPage: boolean;
isPreview: boolean;
isDataManage: boolean;
previewType: string;
modelId: string;
flowId: string;
enableFlow: number;
config: any;
}
defineOptions({ name: 'dynamicModel' });
const { createMessage } = useMessage();
const baseStore = useBaseStore();
const { close } = useTabs();
const state = reactive<State>({
currentView: '',
showPage: false,
isPreview: false,
isDataManage: false,
previewType: '',
modelId: '',
flowId: '',
enableFlow: 0,
config: {},
});
const { currentView, showPage, isPreview, isDataManage, modelId, config } = toRefs(state);
const router = useRouter();
async function init() {
const route = useRoute();
await baseStore.getDictionaryAll();
state.isPreview = (route.query.isPreview as unknown as boolean) || false;
state.isDataManage = (route.query.isDataManage as unknown as boolean) || false;
if (state.isPreview || state.isDataManage) {
if (state.isPreview) {
state.previewType = (route.query.previewType as string) || '';
}
getConfig(route.query.id);
return;
}
state.enableFlow = route.meta.type === 9 ? 1 : 0;
if (!state.enableFlow) return getConfig(route.meta.relationId);
getModelId(route.meta.relationId);
}
function getModelId(flowId) {
state.flowId = flowId;
getFlowStartFormId(flowId)
.then(res => {
if (!res?.data || !res?.data.formId) return;
getConfig(res.data.formId);
})
.catch(() => {
close();
router.replace('/404');
});
}
function getConfig(modelId) {
if (!modelId) return;
state.modelId = modelId;
getConfigData(state.modelId, { type: state.previewType }).then(res => {
if (res.code !== 200 || !res.data) {
close();
router.replace('/404');
createMessage.error(res.msg || '请求出错,请重试');
return;
}
state.config = res.data;
state.config.id = state.config.id || state.modelId;
if (state.enableFlow) {
state.config.enableFlow = state.enableFlow;
state.config.flowId = state.flowId;
}
state.currentView = res.data.webType == '1' ? markRaw(Form) : markRaw(List);
state.showPage = true;
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,142 @@
<template>
<div class="child-table-column">
<template v-if="!expand">
<tr v-for="(item, index) in fewData" class="child-table__row" :key="index">
<td
v-for="(headItem, i) in head"
:key="i"
:style="{ width: `${headItem.width}px`, 'text-align': headItem.align }"
:class="{ 'td-flex-1': !headItem.width }">
<div class="cell" v-if="headItem.yunzhupaasKey === 'relationForm'">
<p class="link-text" :title="item[headItem.dataIndex]" @click="toDetail(headItem.modelId, item[`${headItem.dataIndex}_id`], headItem.propsValue)">
{{ item[headItem.dataIndex] }}
</p>
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="item[headItem.dataIndex]" :precision="headItem.precision" :thousands="headItem.thousands" disabled detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="item[headItem.dataIndex]"
:isStorage="headItem.isStorage"
:precision="headItem.precision"
:thousands="headItem.thousands"
:roundType="headItem.roundType"
detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="item[headItem.dataIndex]" detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="item[headItem.dataIndex]" detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="item[headItem.dataIndex]" :count="headItem.count" :allowHalf="headItem.allowHalf" disabled />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="item[headItem.dataIndex]" :min="headItem.min" :max="headItem.max" :step="headItem.step" disabled />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="item[headItem.dataIndex]" disabled detailed simple v-if="item[headItem.dataIndex]?.length" />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="item[headItem.dataIndex]" disabled detailed simple v-if="item[headItem.dataIndex]?.length" />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="item[headItem.dataIndex]"
:useMask="headItem.useMask"
:maskConfig="headItem.maskConfig"
:showOverflow="showOverflow"
detailed />
</div>
<div class="cell" :class="{ ellipsis: showOverflow }" :title="item[headItem.dataIndex]" v-else>{{ item[headItem.dataIndex] }}</div>
</td>
</tr>
</template>
<template v-if="expand">
<tr v-for="(item, index) in data" class="child-table__row" :key="index">
<td
v-for="(headItem, i) in head"
:key="i"
:style="{ width: `${headItem.width}px`, 'text-align': headItem.align }"
:class="{ 'td-flex-1': !headItem.width }">
<div class="cell" v-if="headItem.yunzhupaasKey === 'relationForm'">
<p class="link-text" :title="item[headItem.dataIndex]" @click="toDetail(headItem.modelId, item[`${headItem.dataIndex}_id`], headItem.propsValue)">
{{ item[headItem.dataIndex] }}
</p>
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="item[headItem.dataIndex]" :precision="headItem.precision" :thousands="headItem.thousands" disabled detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="item[headItem.dataIndex]"
:isStorage="headItem.isStorage"
:precision="headItem.precision"
:thousands="headItem.thousands"
:roundType="headItem.roundType"
detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="item[headItem.dataIndex]" detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'signature '">
<yunzhupaas-signature v-model:value="item[headItem.dataIndex]" detailed />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="item[headItem.dataIndex]" :count="headItem.count" :allowHalf="headItem.allowHalf" disabled />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="item[headItem.dataIndex]" :min="headItem.min" :max="headItem.max" :step="headItem.step" disabled />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="item[headItem.dataIndex]" disabled detailed simple v-if="item[headItem.dataIndex]?.length" />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="item[headItem.dataIndex]" disabled detailed simple v-if="item[headItem.dataIndex]?.length" />
</div>
<div class="cell" v-else-if="headItem.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="item[headItem.dataIndex]"
:useMask="headItem.useMask"
:maskConfig="headItem.maskConfig"
:showOverflow="showOverflow"
detailed />
</div>
<div class="cell" :class="{ ellipsis: showOverflow }" :title="item[headItem.dataIndex]" v-else>{{ item[headItem.dataIndex] }}</div>
</td>
</tr>
</template>
<div class="expand-more-btn" v-if="data && data.length > defaultNumber">
<a-button v-if="expand" type="link" @click="toggleExpand">{{ t('views.dynamicModel.hideSome') }}</a-button>
<a-button v-if="!expand" type="link" @click="toggleExpand">{{ t('views.dynamicModel.showMore') }}</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import type { PropType } from 'vue';
import { useI18n } from '@/hooks/web/useI18n';
defineOptions({ name: 'childTableColumn' });
const props = defineProps({
data: { type: Array as PropType<any[]>, default: () => [] },
head: { type: Array as PropType<any[]>, default: () => [] },
defaultNumber: { type: Number, default: 3 },
expand: { type: Boolean, default: false },
showOverflow: { type: Boolean, default: true },
});
const emit = defineEmits(['toggleExpand', 'toDetail']);
const { t } = useI18n();
const fewData = computed(() => (props.data ? props.data.slice(0, props.defaultNumber) : []));
function toggleExpand() {
emit('toggleExpand');
}
function toDetail(modelId, id, propsValue) {
emit('toDetail', modelId, id, propsValue);
}
</script>

View File

@@ -0,0 +1,244 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :title="config.popupTitle" showOkBtn :okText="getOkText" destroyOnClose @ok="handleSubmit()">
<div class="p-10px" :style="{ margin: '0 auto', width: config.popupWidth || '100%' }">
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</div>
</BasicPopup>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:title="config.popupTitle"
:width="config.popupWidth"
:minHeight="100"
:okText="getOkText"
@ok="handleSubmit()">
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</BasicModal>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
:title="config.popupTitle"
:width="config.popupWidth"
showFooter
:okText="getOkText"
@ok="handleSubmit()">
<div class="p-10px">
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</div>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { createModel, getModelInfo, getConfigData } from '@/api/onlineDev/visualDev';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { reactive, toRefs, nextTick, ref, unref, computed } from 'vue';
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
import { BasicPopup, usePopup } from '@/components/Popup';
import { BasicModal, useModal } from '@/components/Modal';
import { BasicDrawer, useDrawer } from '@/components/Drawer';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import dayjs from 'dayjs';
import { getDateTimeUnit, getParamList } from '@/utils/yunzhupaas';
interface State {
formConf: any;
formData: any;
config: any;
loading: boolean;
key: number;
dataForm: any;
formOperates: any[];
}
const emit = defineEmits(['reload']);
const { createMessage } = useMessage();
const { t } = useI18n();
const userStore = useUserStore();
const [registerPopup, { openPopup, setPopupProps }] = usePopup();
const [registerModal, { openModal, setModalProps }] = useModal();
const [registerDrawer, { openDrawer, setDrawerProps }] = useDrawer();
const parserRef = ref<any>(null);
const state = reactive<State>({
formConf: {},
formData: {},
config: {},
loading: false,
key: +new Date(),
dataForm: {
id: '',
data: '',
},
formOperates: [],
});
const { config, formConf, key, loading } = toRefs(state);
const Parser = createAsyncComponent(() => import('@/components/FormGenerator/src/components/Parser.vue'));
const getOkText = computed(() => {
const text = state.formConf.confirmButtonTextI18nCode
? t(state.formConf.confirmButtonTextI18nCode, state.formConf.confirmButtonText)
: state.formConf.confirmButtonText;
return text || t('common.okText');
});
defineExpose({ init });
function fillFormData(form, data) {
const userInfo = userStore.getUserInfo;
const currDate = new Date();
const loop = (list, parent?) => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.__vModel__) {
if (item.__config__.defaultCurrent) {
if (item.__config__.yunzhupaasKey === 'datePicker') {
item.__config__.defaultValue = dayjs(currDate).startOf(getDateTimeUnit(item.format)).valueOf();
}
if (item.__config__.yunzhupaasKey === 'timePicker') {
item.__config__.defaultValue = dayjs(currDate).format(item.format || 'HH:mm:ss');
}
if (item.__config__.yunzhupaasKey === 'organizeSelect' && userInfo.organizeIdList?.length) {
item.__config__.defaultValue = item.multiple ? [userInfo.organizeIdList] : userInfo.organizeIdList;
}
if (item.__config__.yunzhupaasKey === 'depSelect' && userInfo.departmentId) {
item.__config__.defaultValue = item.multiple ? [userInfo.departmentId] : userInfo.departmentId;
}
if (item.__config__.yunzhupaasKey === 'userSelect' && userInfo.userId) {
item.__config__.defaultValue = item.multiple ? [userInfo.userId] : userInfo.userId;
}
if (item.__config__.yunzhupaasKey === 'usersSelect' && userInfo.userId) {
item.__config__.defaultValue = item.multiple ? [userInfo.userId + '--user'] : userInfo.userId + '--user';
}
if (item.__config__.yunzhupaasKey === 'posSelect' && userInfo.positionIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.positionIds.map(o => o.id) : userInfo.positionIds[0].id;
}
if (item.__config__.yunzhupaasKey === 'roleSelect' && userInfo.roleIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.roleIds : userInfo.roleIds[0];
}
if (item.__config__.yunzhupaasKey === 'groupSelect' && userInfo.groupIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.groupIds : userInfo.groupIds[0];
}
if (item.__config__.yunzhupaasKey === 'sign' && userInfo.signImg) {
item.__config__.defaultValue = userInfo.signImg;
}
}
let val = data.hasOwnProperty(item.__vModel__) ? data[item.__vModel__] : item.__config__.defaultValue;
item.__config__.defaultValue = val;
if (!state.config.isPreview && state.config.useFormPermission) {
let id = item.__config__.isSubTable ? parent.__vModel__ + '-' + item.__vModel__ : item.__vModel__;
let noShow = true;
if (state.formOperates && state.formOperates.length) {
noShow = !state.formOperates.some(o => o.enCode === id);
}
noShow = item.__config__.noShow ? item.__config__.noShow : noShow;
item.__config__.noShow = noShow;
}
}
if (item.__config__ && item.__config__.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children, item);
}
}
};
loop(form.fields);
form.formData = data;
}
function init(data) {
state.config = data;
state.formData = {};
openForm();
nextTick(() => {
setTimeout(() => {
if (state.config.modelId) initData();
}, 0);
});
}
function initData() {
changeLoading(true);
state.loading = true;
getConfigData(state.config.modelId).then(res => {
if (res.code !== 200 || !res.data) return createMessage.error(res.msg || '请求出错,请重试');
if (!res.data.formData) return;
state.formConf = JSON.parse(res.data.formData);
if (state.config.webType == '4' || !state.config.record.id) return setFormValue(state.config.record);
getInfo();
});
}
function getInfo() {
getModelInfo(state.config.recordModelId, state.config.record.id).then(res => {
if (!res.data || !res.data.data) return;
const formData = JSON.parse(res.data.data);
setFormValue({ ...formData, id: state.config.record.id });
});
}
function setFormValue(formData) {
if (state.config.formOptions.length) {
for (let [key, value] of Object.entries(formData)) {
for (let i = 0; i < state.config.formOptions.length; i++) {
const e = state.config.formOptions[i];
if (e.currentField == '@formId') state.formData[e.field] = formData[state.config.rowKey];
if (e.currentField == key) state.formData[e.field] = value;
}
}
}
fillFormData(state.formConf, state.formData);
nextTick(() => {
state.loading = false;
state.key = +new Date();
changeLoading(false);
});
}
function submitForm(data, callback) {
if (!data) return;
setFormProps({ confirmLoading: true });
if (state.config.customBtn) {
const query = { paramList: getParamList(state.config.templateJson, { ...data, id: state.config.record.id }) || [] };
getDataInterfaceRes(state.config.interfaceId, query)
.then(res => {
createMessage.success(res.msg);
if (callback && typeof callback === 'function') callback();
setFormProps({ confirmLoading: false });
setFormProps({ open: false });
if (state.config?.isRefresh) emit('reload');
})
.catch(() => {
setFormProps({ confirmLoading: false });
});
} else {
const formData = { ...state.formData, ...data };
state.dataForm.data = JSON.stringify(formData);
createModel(state.config.modelId, state.dataForm)
.then(res => {
createMessage.success(res.msg);
if (callback && typeof callback === 'function') callback();
setFormProps({ confirmLoading: false });
setFormProps({ open: false });
if (state.config?.isRefresh) emit('reload');
})
.catch(() => {
setFormProps({ confirmLoading: false });
});
}
}
function handleSubmit() {
if (state.config.isPreview) return createMessage.warning('功能预览不支持数据保存');
getParser().handleSubmit();
}
function getParser() {
const parser = unref(parserRef);
if (!parser) throw new Error('parser is null!');
return parser;
}
function openForm() {
if (state.config.popupType === 'fullScreen') return openPopup();
if (state.config.popupType === 'drawer') return openDrawer();
openModal();
}
function setFormProps(data) {
if (state.config.popupType === 'fullScreen') return setPopupProps(data);
if (state.config.popupType === 'drawer') return setDrawerProps(data);
setModalProps(data);
}
function changeLoading(loading) {
setFormProps({ loading });
}
</script>

View File

@@ -0,0 +1,395 @@
<template>
<BasicPopup
v-bind="$attrs"
@register="registerPopup"
showOkBtn
:okText="getOkText"
:cancelText="getCancelText"
destroyOnClose
@ok="handleSubmit"
:closeFunc="onClose"
class="full-popup">
<template #title>
<a-space :size="10">
<div class="text-16px font-medium">{{ title }}</div>
<a-space-compact size="small" block v-if="getShowMoreBtn">
<a-tooltip :title="t('common.prevRecord')">
<a-button size="small" :loading="state.prevBtnLoading" :disabled="getPrevDisabled" @click="handlePrev">
<i class="icon-ym icon-ym-caret-left text-10px"></i>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.nextRecord')">
<a-button size="small" :loading="state.nextBtnLoading" :disabled="getNextDisabled" @click="handleNext">
<i class="icon-ym icon-ym-caret-right text-10px"></i>
</a-button>
</a-tooltip>
</a-space-compact>
</a-space>
</template>
<template #insertToolbar>
<YunzhupaasCheckboxSingle v-model:value="submitType" :label="continueText" v-if="showContinueBtn" />
</template>
<div class="yunzhupaas-common-form-wrapper">
<div class="yunzhupaas-common-form-wrapper__main p-10px" :style="{ margin: '0 auto', width: formConf.fullScreenWidth || '100%' }">
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</div>
<FormExtraPanel v-bind="getFormExtraBind" v-if="state.dataForm.id && formConf.dataLog && !loading" :key="key" />
</div>
</BasicPopup>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:width="formConf.generalWidth"
:minHeight="100"
:okText="getOkText"
:cancelText="getCancelText"
@ok="handleSubmit"
:closeFunc="onClose">
<template #title>
<a-space :size="10">
<div class="text-16px font-medium">{{ title }}</div>
<a-space-compact size="small" block v-if="getShowMoreBtn">
<a-tooltip :title="t('common.prevRecord')">
<a-button size="small" :loading="state.prevBtnLoading" :disabled="getPrevDisabled" @click="handlePrev">
<i class="icon-ym icon-ym-caret-left text-10px"></i>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.nextRecord')">
<a-button size="small" :loading="state.nextBtnLoading" :disabled="getNextDisabled" @click="handleNext">
<i class="icon-ym icon-ym-caret-right text-10px"></i>
</a-button>
</a-tooltip>
</a-space-compact>
</a-space>
</template>
<template #insertFooter>
<div class="float-left mt-5px" v-if="showContinueBtn">
<YunzhupaasCheckboxSingle v-model:value="submitType" :label="continueText" />
</div>
</template>
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</BasicModal>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
:width="formConf.drawerWidth"
showFooter
:okText="getOkText"
:cancelText="getCancelText"
@ok="handleSubmit"
:closeFunc="onClose">
<template #title>
<div class="flex justify-between items-center">
<div class="text-16px font-medium">{{ title }}</div>
<a-space-compact size="small" v-if="getShowMoreBtn">
<a-tooltip :title="t('common.prevRecord')">
<a-button size="small" :loading="state.prevBtnLoading" :disabled="getPrevDisabled" @click="handlePrev">
<i class="icon-ym icon-ym-caret-left text-10px"></i>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.nextRecord')">
<a-button size="small" :loading="state.nextBtnLoading" :disabled="getNextDisabled" @click="handleNext">
<i class="icon-ym icon-ym-caret-right text-10px"></i>
</a-button>
</a-tooltip>
</a-space-compact>
</div>
</template>
<template #insertFooter>
<div class="float-left mt-5px" v-if="showContinueBtn">
<YunzhupaasCheckboxSingle v-model:value="submitType" :label="continueText" />
</div>
</template>
<div class="p-10px">
<Parser ref="parserRef" :formConf="formConf" @submit="submitForm" :key="key" v-if="!loading" />
</div>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { createModel, updateModel, getModelInfo } from '@/api/onlineDev/visualDev';
import { reactive, toRefs, nextTick, ref, unref, computed, inject } from 'vue';
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
import { BasicPopup, usePopup } from '@/components/Popup';
import { BasicModal, useModal } from '@/components/Modal';
import { BasicDrawer, useDrawer } from '@/components/Drawer';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import { useGeneratorStore } from '@/store/modules/generator';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
import { getDateTimeUnit } from '@/utils/yunzhupaas';
import FormExtraPanel from '@/components/FormExtraPanel/index.vue';
interface State {
formConf: any;
defaultFormConf: any;
formData: any;
config: any;
loading: boolean;
prevBtnLoading: boolean;
nextBtnLoading: boolean;
key: number;
dataForm: any;
formOperates: any[];
title: string;
continueText: string;
allList: any[];
currIndex: number;
isContinue: boolean;
submitType: number;
showContinueBtn: boolean;
}
const emit = defineEmits(['reload']);
const getLeftTreeActiveInfo: (() => any) | null = inject('getLeftTreeActiveInfo', null);
const userStore = useUserStore();
const generatorStore = useGeneratorStore();
const { createMessage } = useMessage();
const { t } = useI18n();
const [registerPopup, { openPopup, setPopupProps }] = usePopup();
const [registerModal, { openModal, setModalProps }] = useModal();
const [registerDrawer, { openDrawer, setDrawerProps }] = useDrawer();
const parserRef = ref<any>(null);
const state = reactive<State>({
formConf: {},
defaultFormConf: {},
formData: {},
config: {},
loading: true,
prevBtnLoading: false,
nextBtnLoading: false,
key: +new Date(),
dataForm: {
id: '',
data: '',
},
formOperates: [],
title: '',
continueText: '',
allList: [],
currIndex: 0,
isContinue: false,
submitType: 0,
showContinueBtn: true,
});
const { title, formConf, key, loading, continueText, showContinueBtn, submitType } = toRefs(state);
const Parser = createAsyncComponent(() => import('@/components/FormGenerator/src/components/Parser.vue'));
const getPrevDisabled = computed(() => state.currIndex === 0);
const getNextDisabled = computed(() => state.currIndex === state.allList.length - 1);
const getShowMoreBtn = computed(() => state.config.id && state.config.showMoreBtn && state.formConf.hasConfirmAndAddBtn);
const getOkText = computed(() => {
const text = state.formConf.confirmButtonTextI18nCode
? t(state.formConf.confirmButtonTextI18nCode, state.formConf.confirmButtonText)
: state.formConf.confirmButtonText;
return text || t('common.okText');
});
const getCancelText = computed(() => {
const text = state.formConf.cancelButtonTextI18nCode
? t(state.formConf.cancelButtonTextI18nCode, state.formConf.cancelButtonText)
: state.formConf.cancelButtonText;
return text || t('common.cancelText');
});
const getFormExtraBind = computed(() => ({ showLog: state.formConf.dataLog, modelId: state.config.modelId, formDataId: state.config.id }));
defineExpose({ init });
function fillFormData(form, data, isAdd) {
const userInfo = userStore.getUserInfo;
const currDate = new Date();
const loop = (list, parent?) => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.__vModel__) {
let val = data.hasOwnProperty(item.__vModel__) ? data[item.__vModel__] : item.__config__.defaultValue;
if (!item.__config__.isSubTable) item.__config__.defaultValue = val;
if (isAdd || item.__config__.isSubTable) {
if (item.__config__.defaultCurrent) {
if (item.__config__.yunzhupaasKey === 'datePicker') {
item.__config__.defaultValue = dayjs(currDate).startOf(getDateTimeUnit(item.format)).valueOf();
}
if (item.__config__.yunzhupaasKey === 'timePicker') {
item.__config__.defaultValue = dayjs(currDate).format(item.format || 'HH:mm:ss');
}
if (item.__config__.yunzhupaasKey === 'organizeSelect' && userInfo.organizeIdList?.length) {
item.__config__.defaultValue = item.multiple ? [userInfo.organizeIdList] : userInfo.organizeIdList;
}
if (item.__config__.yunzhupaasKey === 'depSelect' && userInfo.departmentId) {
item.__config__.defaultValue = item.multiple ? [userInfo.departmentId] : userInfo.departmentId;
}
if (item.__config__.yunzhupaasKey === 'userSelect' && userInfo.userId) {
item.__config__.defaultValue = item.multiple ? [userInfo.userId] : userInfo.userId;
}
if (item.__config__.yunzhupaasKey === 'usersSelect' && userInfo.userId) {
item.__config__.defaultValue = item.multiple ? [userInfo.userId + '--user'] : userInfo.userId + '--user';
}
if (item.__config__.yunzhupaasKey === 'posSelect' && userInfo.positionIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.positionIds.map(o => o.id) : userInfo.positionIds[0].id;
}
if (item.__config__.yunzhupaasKey === 'roleSelect' && userInfo.roleIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.roleIds : userInfo.roleIds[0];
}
if (item.__config__.yunzhupaasKey === 'groupSelect' && userInfo.groupIds?.length) {
item.__config__.defaultValue = item.multiple ? userInfo.groupIds : userInfo.groupIds[0];
}
if (item.__config__.yunzhupaasKey === 'sign' && userInfo.signImg) {
item.__config__.defaultValue = userInfo.signImg;
}
}
}
if (isAdd && !item.__config__.isSubTable && data.hasOwnProperty(item.__vModel__)) item.__config__.defaultValue = data[item.__vModel__];
if (!state.config.isPreview && !state.config.isDataManage && state.config.useFormPermission) {
let id = item.__config__.isSubTable ? parent.__vModel__ + '-' + item.__vModel__ : item.__vModel__;
let noShow = true;
if (state.formOperates && state.formOperates.length) {
noShow = !state.formOperates.some(o => o.enCode === id);
}
noShow = item.__config__.noShow ? item.__config__.noShow : noShow;
item.__config__.noShow = noShow;
}
}
if (item.__config__ && item.__config__.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children, item);
}
}
};
loop(form.fields);
form.formData = data;
}
function init(data) {
state.loading = true;
state.submitType = 0;
state.isContinue = false;
state.prevBtnLoading = false;
state.nextBtnLoading = false;
state.title = !data.id || data.id === 'yunzhupaasAdd' ? t('common.add2Text') : t('common.editText');
state.continueText = !data.id ? t('common.continueAndAddText') : t('common.continueText');
state.config = data;
state.defaultFormConf = cloneDeep(data.formConf);
state.formConf = cloneDeep(state.defaultFormConf);
state.showContinueBtn = !data.formData && state.formConf.hasConfirmAndAddBtn;
state.dataForm.id = !data.id || data.id === 'yunzhupaasAdd' ? '' : data.id;
getFormOperates();
openForm();
state.allList = data.allList;
state.currIndex = state.allList.length && data.id ? state.allList.findIndex(item => item.id === data.id) : 0;
nextTick(() => {
if (!data.formData) return setTimeout(initData, 0);
// 行内编辑
setTimeout(() => {
state.formData = { ...data.formData, id: state.dataForm.id };
setFormValue();
}, 0);
});
}
function initData() {
changeLoading(true);
state.loading = true;
if (state.config.id) {
const extra = { modelId: state.config.modelId, id: state.config.id, type: 2 };
generatorStore.setDynamicModelExtra(extra);
getInfo(state.config.id);
} else {
generatorStore.setDynamicModelExtra({});
state.formData = {};
setFormValue(true);
}
}
function getInfo(id) {
getModelInfo(state.config.modelId, id, state.config.menuId).then(res => {
state.dataForm = res.data || {};
if (!state.dataForm.data) return;
state.formData = { ...JSON.parse(state.dataForm.data), id: state.dataForm.id };
setFormValue();
});
}
function setFormValue(isAdd = false) {
if (isAdd && getLeftTreeActiveInfo) state.formData = { ...(getLeftTreeActiveInfo() || {}) };
state.formConf = cloneDeep(state.defaultFormConf);
fillFormData(state.formConf, state.formData, isAdd);
nextTick(() => {
state.key = +new Date();
state.loading = false;
state.prevBtnLoading = false;
state.nextBtnLoading = false;
changeLoading(false);
});
}
function getFormOperates() {
if (state.config.isPreview || state.config.isDataManage || !state.config.useFormPermission) return;
const permissionList = userStore.getPermissionList;
const modelId = state.config.menuId;
const list = permissionList.filter(o => o.modelId === modelId);
state.formOperates = list[0] && list[0].form ? list[0].form : [];
}
function submitForm(data, callback) {
if (!data) return;
setFormProps({ confirmLoading: true });
const formData = { ...state.formData, ...data };
state.dataForm.data = JSON.stringify(formData);
const formMethod = state.dataForm.id ? updateModel : createModel;
formMethod(state.config.modelId, { ...state.dataForm, menuId: state.config.menuId })
.then(res => {
createMessage.success(res.msg);
if (callback && typeof callback === 'function') callback();
setFormProps({ confirmLoading: false });
if (state.submitType == 1) {
initData();
state.isContinue = true;
} else {
setFormProps({ open: false });
emit('reload');
}
})
.catch(() => {
setFormProps({ confirmLoading: false });
});
}
function handleReset() {
getParser().handleReset();
}
function handleSubmit() {
if (state.config.isPreview) return createMessage.warning('功能预览不支持数据保存');
getParser().handleSubmit();
}
function handlePrev() {
state.currIndex--;
// state.prevBtnLoading = true;
handleGetNewInfo();
}
function handleNext() {
state.currIndex++;
// state.nextBtnLoading = true;
handleGetNewInfo();
}
function handleGetNewInfo() {
changeLoading(true);
state.loading = true;
handleReset();
state.config.id = state.allList[state.currIndex].id;
getInfo(state.config.id);
}
function getParser() {
const parser = unref(parserRef);
if (!parser) throw new Error('parser is null!');
return parser;
}
function openForm() {
if (state.formConf.popupType === 'fullScreen') return openPopup();
if (state.formConf.popupType === 'drawer') return openDrawer();
openModal();
}
function setFormProps(data) {
if (state.formConf.popupType === 'fullScreen') return setPopupProps(data);
if (state.formConf.popupType === 'drawer') return setDrawerProps(data);
setModalProps(data);
}
function changeLoading(loading) {
setFormProps({ loading });
}
async function onClose() {
if (state.isContinue) emit('reload');
return true;
}
</script>

View File

@@ -0,0 +1,128 @@
<template>
<Tooltip placement="top">
<template #title>
<span>{{ t('component.table.viewList') }}</span>
</template>
<Popover v-model:open="visible" placement="bottomRight" trigger="click" overlayClassName="yunzhupaas-view-list-popover">
<template #content>
<div class="content">
<div v-for="item in viewList" :key="item.id" class="item" @click="handleClick(item)">
<span class="item-name">{{ item.fullName }}</span>
<div class="item-delete">
<i class="icon-ym icon-ym-app-delete" @click.stop="handleDel(item.id)" v-if="item.type !== 0" />
</div>
<div class="item-default">
<PushpinOutlined :class="{ 'item-default-icon': item.status == 0 }" @click.stop="handleDefault(item)" />
</div>
</div>
</div>
</template>
<menu-outlined />
</Popover>
</Tooltip>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue';
import { reactive, toRefs } from 'vue';
import { Tooltip, Popover } from 'ant-design-vue';
import { MenuOutlined, PushpinOutlined } from '@ant-design/icons-vue';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import { delView, setDefaultView } from '@/api/onlineDev/visualDev';
interface State {
visible: boolean;
}
const state = reactive<State>({
visible: false,
});
const { visible } = toRefs(state);
const emit = defineEmits(['itemClick', 'reload']);
const { t } = useI18n();
const { createMessage, createConfirm } = useMessage();
const props = defineProps({
menuId: { type: String },
viewList: { type: Array as PropType<any[]>, default: () => [] },
});
function handleClick(item) {
emit('itemClick', item);
}
function handleDel(id) {
state.visible = false;
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '确定要删除此视图,是否继续?',
onOk: () => {
delView(id, props.menuId).then(res => {
createMessage.success(res.msg);
emit('reload');
});
},
});
}
function handleDefault(item) {
if (item.status === 1) return;
state.visible = false;
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '设置此视图为默认视图,是否继续?',
onOk: () => {
setDefaultView(item.id, props.menuId).then(res => {
createMessage.success(res.msg);
emit('reload');
});
},
});
}
</script>
<style lang="less">
.yunzhupaas-view-list-popover {
.ant-popover-inner-content {
padding: 0;
.content {
width: 230px;
max-height: 250px;
overflow: auto;
.item {
display: flex;
align-items: center;
margin: 4px 6px;
height: 36px;
padding: 0 8px 0 18px;
border-radius: 6px;
cursor: pointer;
&:hover {
background-color: @selected-hover-bg;
.item-delete i {
display: block;
}
}
.item-name {
flex: 1;
}
.item-delete {
width: 14px;
i {
display: none;
font-size: 14px;
}
}
.item-default {
margin-left: 12px;
i {
font-size: 14px;
}
.item-default-icon {
color: @primary-color;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<Tooltip placement="topRight">
<template #title>
<span>{{ t('component.table.viewSetting') }}</span>
</template>
<setting-outlined @click="openDrawer" />
</Tooltip>
<a-drawer width="340px" v-model:open="visible" class="common-container-drawer">
<template #title>
<a-input v-model:value="dataForm.fullName" :maxlength="6" class="w-130px" />
</template>
<div class="common-container-drawer-body column-setting-body">
<a-tabs v-model:activeKey="activeKey" :tabBarGutter="11" class="average-tabs" @change="onTabChange">
<a-tab-pane :key="0" tab="查询字段" v-if="dataForm.searchList?.length"> </a-tab-pane>
<a-tab-pane :key="1" tab="列表字段"> </a-tab-pane>
</a-tabs>
<div class="flex-1 overflow-auto">
<CheckboxGroup v-model:value="searchCheckedList" ref="searchListRef" class="check-contain" v-if="activeKey === 0">
<div v-for="item in dataForm.searchList" :key="item.id" class="check-item">
<DragOutlined class="table-search-drag-icon" />
<Checkbox :value="item.id">
<div :title="item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label">
{{ item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label }}
</div>
</Checkbox>
</div>
</CheckboxGroup>
<CheckboxGroup v-model:value="columnCheckedList" ref="columnListRef" class="check-contain" v-else>
<div v-for="item in dataForm.columnList" :key="item.id" class="check-item">
<DragOutlined class="table-column-drag-icon" />
<Checkbox :value="item.id">
<div :title="item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label">
{{ item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label }}
</div>
</Checkbox>
</div>
</CheckboxGroup>
</div>
</div>
<div class="h-60px leading-60px yunzhupaas-basic-drawer-footer">
<a-button class="mr-10px" :loading="showAddLoading" @click="addOrUpdateHandle('')">{{ t('common.addView') }}</a-button>
<a-button class="mr-10px" :loading="showUpdateLoading" @click="addOrUpdateHandle(dataForm.id)" type="primary" v-if="currentView.type !== 0">
{{ t('common.updateView') }}
</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { reactive, toRefs, nextTick, unref, ref } from 'vue';
import { Tooltip, Checkbox, CheckboxGroup, Drawer as ADrawer } from 'ant-design-vue';
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import { isNullAndUnDef } from '@/utils/is';
import { cloneDeep } from 'lodash-es';
import Sortablejs from 'sortablejs';
import { createView, updateView } from '@/api/onlineDev/visualDev';
interface State {
dataForm: any;
visible: boolean;
activeKey: number;
searchCheckedList: string[];
columnCheckedList: string[];
showAddLoading: boolean;
showUpdateLoading: boolean;
}
const state = reactive<State>({
dataForm: {
viewName: '',
searchList: [],
columnList: [],
},
visible: false,
activeKey: 0,
searchCheckedList: [],
columnCheckedList: [],
showAddLoading: false,
showUpdateLoading: false,
});
const { dataForm, visible, activeKey, searchCheckedList, columnCheckedList, showAddLoading, showUpdateLoading } = toRefs(state);
const emit = defineEmits(['reload']);
const { t } = useI18n();
const { createMessage } = useMessage();
const searchListRef = ref(null);
const columnListRef = ref(null);
const props = defineProps({
menuId: { type: String, default: '' },
currentView: { type: Object, default: {} },
viewList: { type: Array as PropType<any[]>, default: [] },
});
function openDrawer() {
state.visible = true;
state.showUpdateLoading = false;
state.showAddLoading = false;
state.dataForm = cloneDeep(props.currentView);
initData();
state.activeKey = state.dataForm.searchList?.length ? 0 : 1;
initSortable();
initCheckedList();
}
function initData() {
const defaultData = props.viewList.filter(o => o.type == 0)[0] || {};
state.dataForm.searchList = mergeList(state.dataForm.searchList, defaultData.searchList);
state.dataForm.columnList = mergeList(state.dataForm.columnList, defaultData.columnList);
}
function mergeList(array1: any = [], array2: any = []) {
array2 = array2.map(o => ({ ...o, show: false }));
return [...array1.filter(o => array2.some(i => i.id === o.id)), ...array2.filter(o => !array1.some(i => i.id === o.id))];
}
function initCheckedList() {
state.searchCheckedList = state.dataForm.searchList.filter(o => o.show).map(o => o.id);
state.columnCheckedList = state.dataForm.columnList.filter(o => o.show).map(o => o.id);
}
function onTabChange() {
initSortable();
}
function initSortable() {
nextTick(() => {
const elRef = state.activeKey == 0 ? unref(searchListRef) : unref(columnListRef);
if (!elRef) return;
const el = (elRef as any).$el;
if (!el) return;
Sortablejs.create(unref(el), {
animation: 500,
delay: 400,
delayOnTouchOnly: true,
handle: state.activeKey == 0 ? '.table-search-drag-icon' : '.table-column-drag-icon',
onEnd: evt => {
const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) return;
const list = state.activeKey == 0 ? state.dataForm.searchList : state.dataForm.columnList;
if (oldIndex > newIndex) {
list.splice(newIndex, 0, list[oldIndex]);
list.splice(oldIndex + 1, 1);
} else {
list.splice(newIndex + 1, 0, list[oldIndex]);
list.splice(oldIndex, 1);
}
},
});
});
}
function getRealList(list, checkedList) {
return list.map(o => {
const show = checkedList.findIndex(c => c == o.id) !== -1;
return { ...o, show: show };
});
}
function addOrUpdateHandle(id) {
state.dataForm.searchList = getRealList(state.dataForm.searchList, state.searchCheckedList);
state.dataForm.columnList = getRealList(state.dataForm.columnList, state.columnCheckedList);
const methods = id ? updateView : createView;
const loading = id ? 'showUpdateLoading' : 'showAddLoading';
const query = {
...state.dataForm,
searchList: JSON.stringify(state.dataForm.searchList),
columnList: JSON.stringify(state.dataForm.columnList),
id,
menuId: props.menuId,
};
state[loading] = true;
methods(query)
.then(res => {
createMessage.success(res.msg);
state[loading] = false;
state.visible = false;
emit('reload', state.dataForm.id);
})
.catch(() => {
state[loading] = false;
});
}
</script>
<style lang="less">
.column-setting-body {
display: flex;
flex-direction: column;
width: 100%;
.check-contain {
padding: 10px;
width: 100%;
.check-item {
display: flex;
align-items: center;
min-width: 100%;
padding: 4px 0 8px 0;
.table-search-drag-icon,
.table-column-drag-icon {
margin: 0 5px;
cursor: move;
}
.ant-checkbox-wrapper {
flex: 1;
overflow: hidden;
display: flex;
span:last-child {
flex: 1;
min-width: 0;
div {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
min-width: 0;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,669 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box" v-if="getSearchList?.length">
<BasicForm
@register="registerSearchForm"
:schemas="getSearchList"
@advanced-change="redoHeight"
@submit="handleSearchSubmit"
@reset="handleSearchReset"
class="search-form">
</BasicForm>
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<BasicTable @register="registerTable" v-bind="getTableBindValue" ref="tableRef" @columns-change="handleColumnChange">
<template #tableTitle>
<a-button
v-for="item in state.headerBtnsList"
:key="item.value"
:type="item.value === 'add' ? 'primary' : 'link'"
:preIcon="item.icon"
@click="headBtnsHandle(item.value)">
{{ item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label }}
</a-button>
</template>
<template #expandedRowRender="{ record }" v-if="[1, 2].includes(columnData.type) && getChildTableStyle === 2 && childColumnList.length">
<a-tabs size="small">
<a-tab-pane :key="cIndex" :tab="child.label" :label="child.label" v-for="(child, cIndex) in childColumnList">
<BasicTable @register="registerChildTable" :ellipsis="!!columnData.showOverflow" :data-source="record[child.prop]" :columns="child.children">
<template #bodyCell="{ column = {}, record: childRecord }">
<template v-if="column.yunzhupaasKey === 'relationForm'">
<p class="link-text" @click="toDetail(column.modelId, childRecord[`${column.dataIndex}_id`], childRecord[column.propsValue])">
{{ childRecord[column.dataIndex] }}
</p>
</template>
<template v-if="column.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number
v-model:value="childRecord[column.dataIndex]"
:precision="column.precision"
:thousands="column.thousands"
disabled
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="childRecord[column.dataIndex]"
:isStorage="column.isStorage"
:precision="column.precision"
:thousands="column.thousands"
:roundType="column.roundType"
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="childRecord[column.dataIndex]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="childRecord[column.dataIndex]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="childRecord[column.dataIndex]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="childRecord[column.dataIndex]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="childRecord[column.dataIndex]" disabled detailed simple v-if="childRecord[column.dataIndex]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="childRecord[column.dataIndex]" disabled detailed simple v-if="childRecord[column.dataIndex]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="childRecord[column.dataIndex]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="columnData.showOverflow"
detailed />
</template>
</template>
</BasicTable>
</a-tab-pane>
</a-tabs>
</template>
<template #bodyCell="{ column = {}, record, index }">
<template v-if="column.flag === 'INDEX'">
<span>{{ index + 1 }}</span>
</template>
<template v-for="(item, index) in childColumnList" v-if="getChildTableStyle !== 2 && childColumnList.length">
<template v-if="column.id?.includes('-') && item.children && item.children[0] && column.id === item.children[0]?.id">
<ChildTableColumn
:data="record[item.prop]"
:head="item.children"
@toggleExpand="toggleExpand(record, `${item.prop}Expand`)"
@toDetail="toDetail"
:expand="record[`${item.prop}Expand`]"
:showOverflow="columnData.showOverflow"
:key="index" />
</template>
</template>
<template v-if="!(record.top || column.id?.includes('-'))">
<template v-if="column.yunzhupaasKey === 'relationForm'">
<p class="link-text" @click="toDetail(column.modelId, record[`${column.prop}_id`], column.propsValue)">{{ record[column.prop] }}</p>
</template>
<template v-if="column.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="record[column.prop]" :precision="column.precision" :thousands="column.thousands" disabled detailed />
</template>
<template v-if="column.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="record[column.prop]"
:isStorage="column.isStorage"
:precision="column.precision"
:thousands="column.thousands"
:roundType="column.roundType"
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.prop]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.prop]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.prop]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="columnData.showOverflow"
detailed />
</template>
</template>
<template v-if="column.key === 'action' && (!record.top || columnData.type == 5)">
<TableAction :actions="getTableActions(record, index)" />
</template>
</template>
<template #summary v-if="columnData.showSummary && [1, 2, 4].includes(columnData.type)">
<a-table-summary fixed>
<a-table-summary-row>
<a-table-summary-cell :index="0">{{ t('component.table.summary') }}</a-table-summary-cell>
<a-table-summary-cell v-for="(item, index) in getColumnSum" :key="index" :index="index + 1" :align="getSummaryCellAlign(index)">
{{ item }}
</a-table-summary-cell>
<a-table-summary-cell :index="getColumnSum.length + 1"></a-table-summary-cell>
</a-table-summary-row>
</a-table-summary>
</template>
</BasicTable>
</div>
</div>
<Form ref="formRef" @reload="reload" />
<Detail ref="detailRef" />
</div>
</template>
<script lang="ts" setup>
import { getModelList, batchDelete, getConfigData } from '@/api/onlineDev/visualDev';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { ref, reactive, onMounted, toRefs, computed, unref, nextTick } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import { useBaseStore } from '@/store/modules/base';
import { BasicForm, useForm } from '@/components/Form';
import { BasicTable, useTable, TableAction, ActionItem, TableActionType, SorterResult } from '@/components/Table';
import Form from '../Form.vue';
import Detail from '../detail/index.vue';
import ChildTableColumn from '../ChildTableColumn.vue';
import { getScriptFunc, onlineUtils, thousandsFormat } from '@/utils/yunzhupaas';
import { getSearchFormSchemas } from '@/components/FormGenerator/src/helper/transform';
import { dyOptionsList } from '@/components/FormGenerator/src/helper/config';
import { cloneDeep } from 'lodash-es';
import { usePermission } from '@/hooks/web/usePermission';
interface State {
config: any;
columnData: any;
formConf: any;
headerBtnsList: any[];
columnBtnsList: any[];
columnOptions: any[];
columns: any[];
childColumnList: any[];
cacheList: any[];
columnSettingList: any[];
searchSchemas: any[];
customRow: any;
tabActiveKey: any;
tabList: any[];
}
const props = defineProps(['config', 'detailFormData', 'isPreview', 'isDataManage']);
const emit = defineEmits(['openDetail', 'openForm']);
const { hasBtnP } = usePermission();
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const userStore = useUserStore();
const baseStore = useBaseStore();
const formRef = ref<any>(null);
const tableRef = ref<Nullable<TableActionType>>(null);
const detailRef = ref<any>(null);
const searchInfo = reactive({
modelId: '',
menuId: '',
queryJson: '',
superQueryJson: '',
extraQueryJson: '',
});
const state = reactive<State>({
config: {},
columnData: {},
formConf: {},
headerBtnsList: [],
columnBtnsList: [],
columnOptions: [],
columns: [],
childColumnList: [],
cacheList: [],
columnSettingList: [],
searchSchemas: [],
customRow: null,
tabActiveKey: '',
tabList: [],
});
const { columnData, childColumnList } = toRefs(state);
const [registerSearchForm, { updateSchema, submit: searchFormSubmit }] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
});
const [registerTable, { reload, setLoading, clearSelectedRowKeys, redoHeight }] = useTable({
api: getModelList,
immediate: false,
clickToRowSelect: false,
tableSetting: { setting: false, redo: !props.isPreview },
afterFetch: data => {
state.cacheList = cloneDeep(data);
return data;
},
});
const [registerChildTable] = useTable({
pagination: false,
canResize: false,
showTableSetting: false,
});
defineExpose({ reload });
const getPagination = computed(() => {
if ([3, 5].includes(state.columnData.type) || !state.columnData.hasPage) return false;
return { pageSize: state.columnData.pageSize };
});
const getChildTableStyle = computed(() => (state.columnData.type == 3 || state.columnData.type == 5 ? 1 : state.columnData.childTableStyle));
const getColumns = computed(() => state.columns);
const getSearchList = computed(() => {
return cloneDeep(state.searchSchemas).map(o => ({ ...o, show: true }));
});
const getRowKey = computed(() => (state.config.webType == 4 && state.columnData.viewKey ? state.columnData.viewKey : 'id'));
const getTableBindValue = computed(() => {
let columns = unref(getColumns);
const defaultSortConfig = (state.columnData.defaultSortConfig || []).map(o => (o.sort === 'desc' ? '-' : '') + o.field);
const data: any = {
pagination: unref(getPagination),
searchInfo: unref(searchInfo),
defSort: { sidx: defaultSortConfig.join(',') },
sortFn: (sortInfo: SorterResult | SorterResult[]) => {
if (Array.isArray(sortInfo)) {
const sortList = sortInfo.map(o => (o.order === 'descend' ? '-' : '') + o.field);
return { sidx: sortList.join(',') };
} else {
const { field, order } = sortInfo;
if (field && order) {
// 排序字段
return { sidx: (order === 'descend' ? '-' : '') + field };
} else {
return {};
}
}
},
columns,
clearSelectOnPageChange: true,
bordered: (unref(getChildTableStyle) != 2 && !!state.childColumnList?.length) || !!state.columnData.complexHeaderList?.length,
rowKey: unref(getRowKey),
};
if (state.columnBtnsList.length) {
let columnBtnsLen = state.columnBtnsList.length;
data.actionColumn = {
width: columnBtnsLen * 50,
title: t('component.table.action'),
dataIndex: 'action',
fixed: 'right',
};
}
if (state.customRow) data.customRow = state.customRow;
return data;
});
const getSummaryColumn = computed(() => {
let defaultColumns = unref(getColumns);
// 处理列固定
if (state.columnSettingList?.length) {
for (let i = 0; i < defaultColumns.length; i++) {
inner: for (let j = 0; j < state.columnSettingList.length; j++) {
if (defaultColumns[i].dataIndex === state.columnSettingList[j].dataIndex) {
defaultColumns[i].fixed = state.columnSettingList[j].fixed;
defaultColumns[i].visible = state.columnSettingList[j].visible;
break inner;
}
}
}
defaultColumns = defaultColumns.filter(o => o.visible);
}
let columns: any[] = [];
for (let i = 0; i < defaultColumns.length; i++) {
const e = defaultColumns[i];
if (e.yunzhupaasKey === 'table' || e.yunzhupaasKey === 'complexHeader') {
if (e.children?.length) columns.push(...e.children);
} else {
columns.push(e);
}
if (e.fixed && e.children?.length) {
for (let j = 0; j < e.children.length; j++) {
e.children[j].fixed = e.fixed;
}
}
}
const leftFixedList = columns.filter(o => o.fixed === 'left');
const rightFixedList = columns.filter(o => o.fixed === 'right');
const noFixedList = columns.filter(o => o.fixed !== 'left' && o.fixed !== 'right');
return [...leftFixedList, ...noFixedList, ...rightFixedList];
});
// 列表合计
const getColumnSum = computed(() => {
const sums: any[] = [];
const isSummary = key => state.columnData.summaryField.includes(key);
const useThousands = key => unref(getSummaryColumn).some(o => o.__vModel__ === key && o.thousands);
unref(getSummaryColumn).forEach((column, index) => {
let sumVal = state.cacheList.reduce((sum, d) => sum + getCmpValOfRow(d, column.prop), 0);
if (!isSummary(column.prop)) sumVal = '';
sumVal = Number.isNaN(sumVal) ? '' : sumVal;
const realVal = sumVal && !Number.isInteger(sumVal) ? Number(sumVal).toFixed(2) : sumVal;
sums[index] = useThousands(column.prop) ? thousandsFormat(realVal) : realVal;
});
if ([1, 2].includes(state.columnData.type) && unref(getChildTableStyle) === 2 && state.childColumnList.length) sums.unshift('');
return sums;
});
function getCmpValOfRow(row, key) {
const isSummary = key => state.columnData.summaryField.includes(key);
if (!state.columnData.summaryField.length || !isSummary(key)) return 0;
const target = row[key];
if (!target) return 0;
const data = isNaN(target) ? 0 : Number(target);
return data;
}
function getSummaryCellAlign(index) {
if (!unref(getSummaryColumn).length) return;
return unref(getSummaryColumn)[index]?.align || 'left';
}
function getTableActions(record, index): ActionItem[] {
const list = state.columnBtnsList.map(o => {
const item: ActionItem = {
label: o.labelI18nCode ? t(o.labelI18nCode, o.label) : o.label,
onClick: columnBtnsHandle.bind(null, o.value, record),
};
if (o.value === 'remove') item.color = 'error';
if (state.config.enableFlow) {
if (o.value === 'edit') item.disabled = ![0, 8, 9].includes(record.flowState);
if (o.value === 'remove') item.disabled = ![0, 9].includes(record.flowState);
if (o.value === 'detail') item.disabled = !record.flowState;
} else {
if (o?.event?.enableFunc) {
const parameter = { row: record, rowIndex: index, onlineUtils };
const func: any = getScriptFunc(o.event.enableFunc);
item.disabled = (func && !func(parameter)) || false;
}
}
return item;
});
return list;
}
function addHandle() {
const data = {
id: '',
formConf: transferExtraList(state.formConf),
modelId: searchInfo.modelId,
isPreview: props.isPreview,
useFormPermission: state.columnData.useFormPermission,
showMoreBtn: ![3, 5].includes(state.columnData.type),
menuId: searchInfo.menuId,
allList: state.cacheList,
};
emit('openForm', data);
}
// 顶部按钮点击事件
function headBtnsHandle(key) {
if (key === 'add') return addHandle();
}
// 行按钮点击事件
function columnBtnsHandle(key, record) {
if (key === 'edit') return updateHandle(record);
if (key === 'detail') return goDetail(record);
if (key == 'remove') handleDelete(record.id);
}
// 编辑
function updateHandle(record) {
const data = {
id: record.id,
formConf: state.formConf,
modelId: searchInfo.modelId,
isPreview: props.isPreview,
useFormPermission: state.columnData.useFormPermission,
showMoreBtn: ![3, 5].includes(state.columnData.type),
menuId: searchInfo.menuId,
allList: state.cacheList,
};
emit('openForm', data);
}
// 查看详情
function goDetail(record) {
const data = {
id: record.id,
formConf: state.formConf,
modelId: searchInfo.modelId,
menuId: searchInfo.menuId,
useFormPermission: state.columnData.useFormPermission,
title: state.config.fullName,
hideExtra: true,
};
emit('openDetail', data);
}
function handleDelete(id) {
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: t('common.delTip'),
onOk: () => {
const query = { ids: [id], flowId: state.config.flowId || '' };
batchDelete(searchInfo.modelId, query).then(res => {
createMessage.success(res.msg);
reload();
});
},
});
}
function init() {
state.config = {
modelId: searchInfo.modelId,
isPreview: props.isPreview,
...props.config.extraConfig,
};
searchInfo.modelId = state.config.id;
searchInfo.menuId = props.config.targetFormId;
const obj = {};
obj[props.config.targetField] = props.detailFormData[props.config.currentField + '_yunzhupaasId'];
searchInfo.extraQueryJson = JSON.stringify(obj) === '{}' ? '' : JSON.stringify(obj);
if (!state.config.columnData || (state.config.webType != '4' && !state.config.formData)) return;
state.columnData = JSON.parse(state.config.columnData);
state.formConf = state.config.formData ? JSON.parse(state.config.formData) : {};
const columnBtnsList = state.columnData.columnBtnsList || [];
getHeaderBtnsList(state.columnData.btnsList.filter(o => o.value === 'add') || []);
getColumnBtnsList(columnBtnsList);
state.columnOptions = state.columnData.columnOptions || [];
if (!unref(getPagination)) (searchInfo as any).pageSize = 1000000;
setLoading(true);
getSearchSchemas();
getColumnList();
if (props.isPreview) return setLoading(false);
nextTick(() => {
unref(getSearchList)?.length ? searchFormSubmit() : reload({ page: 1 });
});
}
function getHeaderBtnsList(btnsList) {
btnsList = btnsList.filter(o => o.show || !Reflect.has(o, 'show'));
if (props.isPreview || props.isDataManage || !state.columnData.useBtnPermission) return (state.headerBtnsList = btnsList);
// 过滤权限
let btns: any[] = [];
for (let i = 0; i < btnsList.length; i++) {
if (hasBtnP('btn_' + btnsList[i].value, true, searchInfo.menuId)) btns.push(btnsList[i]);
}
state.headerBtnsList = btns;
}
function getColumnBtnsList(columnBtnsList) {
columnBtnsList = columnBtnsList.filter(o => o.show || !Reflect.has(o, 'show'));
let btns: any[] = [];
if (props.isPreview || props.isDataManage || !state.columnData.useBtnPermission) {
btns = columnBtnsList;
} else {
// 过滤权限
const permissionList = userStore.getPermissionList;
const list = permissionList.filter(o => o.modelId === searchInfo.menuId);
const perBtnList = list[0] && list[0].button ? list[0].button : [];
for (let i = 0; i < columnBtnsList.length; i++) {
inner: for (let j = 0; j < perBtnList.length; j++) {
if ('btn_' + columnBtnsList[i].value === perBtnList[j].enCode) {
btns.push(columnBtnsList[i]);
break inner;
}
}
}
}
state.columnBtnsList = btns;
}
function getSearchSchemas() {
const schemas = getSearchFormSchemas(state.columnData.searchList);
schemas.forEach(cur => {
const config = cur.__config__;
if (dyOptionsList.includes(config.yunzhupaasKey)) {
if (config.dataType === 'dictionary' && config.dictionaryType) {
baseStore.getDicDataSelector(config.dictionaryType).then(res => {
updateSchema([{ field: cur.field, componentProps: { options: res } }]);
});
}
if (config.dataType === 'dynamic' && config.propsUrl) {
const query = { paramList: config.templateJson || [] };
getDataInterfaceRes(config.propsUrl, query).then(res => {
const data = Array.isArray(res.data) ? res.data : [];
updateSchema([{ field: cur.field, componentProps: { options: data } }]);
});
}
}
if ((Array.isArray(cur.value) && cur.value.length) || cur.value || cur.value === 0 || cur.value === false) cur.defaultValue = cur.value;
});
state.searchSchemas = schemas;
}
// 获取列
function getColumnList() {
let columnList: any[] = [];
if (props.isPreview || props.isDataManage || !state.columnData.useColumnPermission) {
columnList = state.columnData.columnList;
} else {
// 过滤权限
const permissionList = userStore.getPermissionList;
const list = permissionList.filter(o => o.modelId === searchInfo.menuId);
const perColumnList = list[0] && list[0].column ? list[0].column : [];
for (let i = 0; i < state.columnData.columnList.length; i++) {
inner: for (let j = 0; j < perColumnList.length; j++) {
if (state.columnData.columnList[i].prop === perColumnList[j].enCode) {
columnList.push(state.columnData.columnList[i]);
break inner;
}
}
}
}
let columns = columnList.map(o => ({
...o,
placeholder: state.columnData.type == 4 && o.placeholderI18nCode ? t(o.placeholderI18nCode, o.placeholder) : o.placeholder,
title: o.labelI18nCode ? t(o.labelI18nCode, o.label) : o.label,
dataIndex: o.prop,
align: o.align,
fixed: o.fixed == 'none' ? false : o.fixed,
sorter: o.sortable ? { multiple: 1 } : o.sortable,
width: o.width || 100,
}));
if (state.columnData.type !== 3 && state.columnData.type !== 5) columns = getComplexColumns(columns);
state.columns = columns.filter(o => o.prop.indexOf('-') < 0);
}
function getComplexColumns(columns) {
let complexHeaderList: any[] = state.columnData.complexHeaderList || [];
if (!complexHeaderList.length) return columns;
let childColumns: any[] = [];
let firstChildColumns: string[] = [];
for (let i = 0; i < complexHeaderList.length; i++) {
const e = complexHeaderList[i];
e.label = e.fullName;
e.labelI18nCode = e.fullNameI18nCode;
e.title = e.fullNameI18nCode ? t(e.fullNameI18nCode, e.fullName) : e.fullName;
e.align = e.align;
e.dataIndex = e.id;
e.prop = e.id;
e.children = [];
e.yunzhupaasKey = 'complexHeader';
if (e.childColumns?.length) {
childColumns.push(...e.childColumns);
for (let k = 0; k < e.childColumns.length; k++) {
const item = e.childColumns[k];
for (let j = 0; j < columns.length; j++) {
const o = columns[j];
if (o.prop == item && o.fixed !== 'left' && o.fixed !== 'right') e.children.push({ ...o });
}
}
}
if (e.children.length) firstChildColumns.push(e.children[0].prop);
}
complexHeaderList = complexHeaderList.filter(o => o.children.length);
let list: any[] = [];
for (let i = 0; i < columns.length; i++) {
const e = columns[i];
if (!childColumns.includes(e.prop) || e.fixed === 'left' || e.fixed === 'right') {
list.push(e);
} else {
if (firstChildColumns.includes(e.prop)) {
const item = complexHeaderList.find(o => o.childColumns.includes(e.prop));
list.push(item);
}
}
}
return list;
}
function toggleExpand(row, field) {
row[field] = !row[field];
}
// 关联表单查看详情
function toDetail(modelId, id, propsValue) {
if (!id) return;
getConfigData(modelId).then(res => {
if (!res.data || !res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = 'general';
formConf.customBtns = [];
formConf.hasPrintBtn = false;
const data = { id, formConf, modelId, propsValue };
detailRef.value?.init(data);
});
}
function handleColumnChange(data) {
state.columnSettingList = data;
}
function handleSearchSubmit(data) {
if (props.isPreview) return;
clearSelectedRowKeys();
let obj = {};
for (let [key, value] of Object.entries(data)) {
if (value || value === 0 || value === false) {
if (Array.isArray(value)) {
if (value.length) obj[key] = value;
} else {
obj[key] = value;
}
}
}
searchInfo.queryJson = JSON.stringify(obj) === '{}' ? '' : JSON.stringify(obj);
reload({ page: 1 });
}
function handleSearchReset() {
searchFormSubmit();
}
function transferExtraList(formConf) {
if (!props.config?.formOptions?.length) return formConf;
const loop = list => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item?.__config__?.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children);
}
if (item.__vModel__) {
for (let j = 0; j < props.config?.formOptions.length; j++) {
const element = props.config?.formOptions[j];
if (element.targetField == item.__vModel__) {
item.__config__.defaultValue = props.detailFormData[element.currentField + '_yunzhupaasId'];
item.__config__.defaultCurrent = false;
}
}
}
}
};
loop(formConf.fields);
return formConf;
}
onMounted(() => init());
</script>

View File

@@ -0,0 +1,716 @@
<template>
<a-col
:class="[...(getConfig.className || []), getConfig.layout === 'colFormItem' ? 'ant-col-item' : '']"
:span="getConfig.span"
v-if="!getConfig.noShow && (!getConfig.visibility || (Array.isArray(getConfig.visibility) && getConfig.visibility.includes('pc')))">
<template v-if="getConfig.layout === 'colFormItem'">
<template v-if="getConfig.yunzhupaasKey === 'divider'">
<yunzhupaas-divider :contentPosition="getItem.contentPosition" :content="getItem.content" />
</template>
<template v-else>
<a-form-item :name="getItem.__vModel__" :labelCol="getLabelCol">
<template #label v-if="getConfig.showLabel">
{{ getLabel ? getLabel + (formConf.labelSuffix || '') : '' }}
<BasicHelp :text="getTipLabel" v-if="getLabel && getTipLabel" />
</template>
<template v-if="getConfig.yunzhupaasKey === 'text'">
<yunzhupaas-text :content="getItem.content" :textStyle="getItem.textStyle" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'link'">
<yunzhupaas-link :content="getItem.content" :href="getItem.href" :target="getItem.target" :textStyle="getItem.textStyle" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'alert'">
<yunzhupaas-alert
:title="getItem.title"
:type="getItem.type"
:closable="getItem.closable"
:showIcon="getItem.showIcon"
:description="getItem.description"
:closeText="getItem.closeText" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'groupTitle'">
<yunzhupaas-group-title :content="getItem.content" :contentPosition="getItem.contentPosition" :helpMessage="getItem.helpMessage" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'button'">
<yunzhupaas-button :align="getItem.align" :buttonText="getItem.buttonText" :type="getItem.type" :disabled="getItem.disabled" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="getConfig.defaultValue" detailed disabled />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="getConfig.defaultValue" detailed disabled />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'colorPicker'">
<yunzhupaas-color-picker v-model:value="getConfig.defaultValue" :showAlpha="getItem.showAlpha" :colorFormat="getItem.colorFormat" disabled />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="getConfig.defaultValue" :count="getItem.count" :allowHalf="getItem.allowHalf" disabled />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="getConfig.defaultValue" :min="getItem.min" :max="getItem.max" :step="getItem.step" disabled />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'editor'">
<div v-html="getConfig.defaultValue"></div>
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'relationForm'">
<p class="link-text leading-32px" @click="toDetail(item)">{{ getItem.name || getConfig.defaultValue }}</p>
<ExtraRelationInfo
:extraOptions="getItem.extraOptions"
:data="extraData"
v-if="getItem.extraOptions?.length && extraData && JSON.stringify(extraData) !== '{}'" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'popupSelect'">
<p class="leading-32px">{{ getItem.name || getConfig.defaultValue }}</p>
<ExtraRelationInfo
:extraOptions="getItem.extraOptions"
:data="extraData"
v-if="getItem.extraOptions?.length && extraData && JSON.stringify(extraData) !== '{}'" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'barcode'">
<yunzhupaas-barcode
:format="getItem.format"
:lineColor="getItem.lineColor"
:background="getItem.background"
:width="getItem.width"
:height="getItem.height"
:staticText="getItem.staticText"
:dataType="getItem.dataType"
:relationField="getItem.relationField + '_id'"
:formData="formData" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'qrcode'">
<yunzhupaas-qrcode
:format="getItem.format"
:colorLight="getItem.colorLight"
:colorDark="getItem.colorDark"
:width="getItem.width"
:staticText="getItem.staticText"
:dataType="getItem.dataType"
:relationField="getItem.relationField + '_id'"
:formData="formData" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number
v-model:value="getConfig.defaultValue"
:precision="getItem.precision"
:addonBefore="getItem.addonBefore"
:addonAfter="getItem.addonAfter"
:thousands="getItem.thousands"
:isAmountChinese="getItem.isAmountChinese"
disabled
detailed />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
:expression="getItem.expression"
:isStorage="getItem.isStorage"
:formData="formData"
:precision="getItem.precision"
:thousands="getItem.thousands"
:isAmountChinese="getItem.isAmountChinese"
:roundType="getItem.roundType"
detailed />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'location'">
<yunzhupaas-location v-model:value="getConfig.defaultValue" :enableLocationScope="getItem.enableLocationScope" detailed />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="getConfig.defaultValue" detailed />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="getConfig.defaultValue" detailed />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'iframe'">
<yunzhupaas-iframe
:href="getItem.href"
:height="getItem.height"
:borderType="getItem.borderType"
:borderColor="getItem.borderColor"
:borderWidth="getItem.borderWidth" />
</template>
<template v-else-if="getConfig.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="getConfig.defaultValue"
:addonBefore="getItem.addonBefore"
:addonAfter="getItem.addonAfter"
:useMask="getItem.useMask"
:maskConfig="getItem.maskConfig"
detailed />
</template>
<template v-else>
<p>{{ getValue(getItem.__config__?.defaultValue || '') }}</p>
</template>
</a-form-item>
</template>
</template>
<template v-else>
<template v-if="getConfig.yunzhupaasKey === 'card'">
<a-card :hoverable="getItem.shadow === 'hover'" :size="formConf.size">
<template #title v-if="getItem.header">{{ getItem.header }}<BasicHelp :text="getTipLabel" v-if="getTipLabel" /></template>
<a-row>
<Item v-for="(childItem, childIndex) in getConfig.children" v-bind="getBindValue" :key="childIndex" :item="childItem" @toDetail="toDetail" />
</a-row>
</a-card>
</template>
<a-row v-if="getConfig.yunzhupaasKey === 'row'">
<Item v-for="(childItem, childIndex) in getConfig.children" v-bind="getBindValue" :key="childIndex" :item="childItem" @toDetail="toDetail" />
</a-row>
<template v-if="getConfig.yunzhupaasKey === 'tab'">
<a-tabs :type="getItem.type" :tabPosition="getItem.tabPosition" :size="formConf.size" v-model:activeKey="getConfig.active">
<a-tab-pane v-for="pane in getConfig.children" :key="pane.name" :tab="pane.titleI18nCode ? t(pane.titleI18nCode, pane.title) : pane.title">
<a-row>
<Item
v-for="(childItem, childIndex) in pane.__config__.children"
v-bind="getBindValue"
:key="childIndex"
:item="childItem"
@toDetail="toDetail" />
</a-row>
</a-tab-pane>
</a-tabs>
</template>
<template v-if="getConfig.yunzhupaasKey === 'steps'">
<a-steps :size="formConf.size" v-model:current="getConfig.active" :type="item.simple ? 'navigation' : 'default'" :status="item.processStatus">
<a-step v-for="step in getConfig.children" :key="step.name" :title="step.titleI18nCode ? t(step.titleI18nCode, step.title) : step.title">
<template #icon v-if="step.icon">
<span :class="step.icon + ' custom-icon'"></span>
</template>
</a-step>
</a-steps>
<a-row v-for="(step, stepIndex) in getConfig.children" :key="step.name" class="!pt-12px w-full" v-show="getConfig.active === stepIndex">
<Item v-for="(childItem, childIndex) in step.__config__.children" v-bind="getBindValue" :key="childIndex" :item="childItem" @toDetail="toDetail" />
</a-row>
</template>
<template v-if="getConfig.yunzhupaasKey === 'collapse'">
<a-collapse :ghost="getItem.ghost" :expandIconPosition="getItem.expandIconPosition" :accordion="getItem.accordion" v-model:activeKey="getConfig.active">
<a-collapse-panel v-for="pane in getConfig.children" :key="pane.name" :header="pane.titleI18nCode ? t(pane.titleI18nCode, pane.title) : pane.title">
<a-row>
<Item
v-for="(childItem, childIndex) in pane.__config__.children"
v-bind="getBindValue"
:key="childIndex"
:item="childItem"
@toDetail="toDetail" />
</a-row>
</a-collapse-panel>
</a-collapse>
</template>
<template v-if="getConfig.yunzhupaasKey === 'table' && !getConfig.noShow">
<div class="yunzhupaas-child-list" v-if="item.layoutType === 'list'">
<a-collapse expandIconPosition="end" :bordered="false" class="outer-collapse" v-model:activeKey="outerActiveKey">
<a-collapse-panel forceRender>
<template #header>
<span class="min-h-22px inline-block">{{ getConfig.showTitle ? getLabel : '' }}</span>
<BasicHelp :text="getTipLabel" v-if="getConfig.showTitle && getLabel && getTipLabel" />
</template>
<a-collapse :bordered="false" v-model:activeKey="innerActiveKey">
<template #expandIcon="{ isActive }">
<CaretRightOutlined :rotate="isActive ? 90 : 0" />
</template>
<a-collapse-panel v-for="(record, index) in getDefaultValue" :key="record.yunzhupaasId" forceRender>
<template #header>
<span class="min-h-22px inline-block">{{ getConfig.showTitle ? getLabel : '' }}({{ index + 1 }})</span>
</template>
<a-row :gutter="formConf.formStyle ? 0 : formConf.gutter">
<a-col :span="column.__config__.span" v-for="column in getColumns" :key="column.__config__.formId">
<a-form-item :labelCol="column.labelCol">
<template #label v-if="column.__config__.showLabel">
{{ column.title ? column.title + (column.__config__.labelSuffix || '') : '' }}
<BasicHelp :text="column.tipLabel" v-if="column.title && column.tipLabel" />
</template>
<template v-if="column.__config__?.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="record[column.dataIndex]" detailed disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="record[column.dataIndex]" detailed disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'colorPicker'">
<yunzhupaas-color-picker
v-model:value="record[column.dataIndex]"
:showAlpha="column.showAlpha"
:colorFormat="column.colorFormat"
disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.dataIndex]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.dataIndex]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'relationForm'">
<p class="link-text" @click="toTableDetail(column, record[column.dataIndex + '_id'])">{{ record[column.dataIndex] }}</p>
</template>
<template v-else-if="['relationFormAttr', 'popupAttr'].includes(column.__config__?.yunzhupaasKey)">
<p v-if="!record[column.dataIndex]">{{ record[column.relationField.split('_yunzhupaasTable_')[0] + '_' + column.showField] }}</p>
<p v-else>{{ record[column.dataIndex] }}</p>
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number
v-model:value="record[column.dataIndex]"
:precision="column.precision"
:addonBefore="column.addonBefore"
:addonAfter="column.addonAfter"
:thousands="column.thousands"
:isAmountChinese="column.isAmountChinese"
disabled
detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
:rowIndex="index"
:expression="column.expression"
:isStorage="column.isStorage"
:formData="formData"
:precision="column.precision"
:thousands="column.thousands"
:isAmountChinese="column.isAmountChinese"
:roundType="column.roundType"
detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'location'">
<yunzhupaas-location v-model:value="record[column.dataIndex]" :enableLocationScope="column.enableLocationScope" detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="record[column.dataIndex]" detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="record[column.dataIndex]" detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.dataIndex]"
:addonBefore="column.addonBefore"
:addonAfter="column.addonAfter"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
detailed />
</template>
<template v-else>
<p>{{ getValue(record[column.dataIndex]) }}</p>
</template>
</a-form-item>
</a-col>
</a-row>
</a-collapse-panel>
<a-collapse-panel key="summary" v-if="getDefaultValue.length && item.showSummary">
<template #header>
<span class="min-h-22px inline-block">合计</span>
</template>
<a-row :gutter="formConf.formStyle ? 0 : formConf.gutter">
<template v-for="(column, cIndex) in getColumns" :key="column.__config__.formId">
<a-col :span="column.__config__.span" v-if="column.dataIndex && item.summaryField.includes(column.dataIndex)">
<a-form-item :labelCol="column.labelCol">
<template #label v-if="column.__config__.showLabel">
{{ column.title ? column.title + (column.__config__.labelSuffix || '') : '' }}
<BasicHelp :text="column.tipLabel" v-if="column.title && column.tipLabel" />
</template>
<YunzhupaasInput :value="getColumnSum[cIndex]" disabled detailed :style="column.style" />
</a-form-item>
</a-col>
</template>
</a-row>
</a-collapse-panel>
</a-collapse>
</a-collapse-panel>
</a-collapse>
</div>
<a-form-item v-else>
<YunzhupaasGroupTitle :content="getLabel" v-if="getConfig.showTitle && getLabel" :helpMessage="getTipLabel" :bordered="false" />
<a-table
:data-source="getItem.__config__.defaultValue"
:columns="getColumns"
size="small"
:pagination="false"
:scroll="{ x: 'max-content' }"
:bordered="formConf.formStyle === 'word-form' || !!getConfig?.complexHeaderList?.length">
<template #headerCell="{ column }">
{{ column.title }}
<BasicHelp v-if="column.title && column.tipLabel" :text="column.tipLabel" />
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="record[column.dataIndex]" detailed disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="record[column.dataIndex]" detailed disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'colorPicker'">
<yunzhupaas-color-picker v-model:value="record[column.dataIndex]" :showAlpha="column.showAlpha" :colorFormat="column.colorFormat" disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.dataIndex]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.dataIndex]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'relationForm'">
<p class="link-text" @click="toTableDetail(column, record[column.dataIndex + '_id'])">{{ record[column.dataIndex] }}</p>
</template>
<template v-else-if="['relationFormAttr', 'popupAttr'].includes(column.__config__?.yunzhupaasKey)">
<p v-if="!record[column.dataIndex]">{{ record[column.relationField.split('_yunzhupaasTable_')[0] + '_' + column.showField] }}</p>
<p v-else>{{ record[column.dataIndex] }}</p>
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number
v-model:value="record[column.dataIndex]"
:precision="column.precision"
:addonBefore="column.addonBefore"
:addonAfter="column.addonAfter"
:thousands="column.thousands"
:isAmountChinese="column.isAmountChinese"
disabled
detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
:rowIndex="index"
:expression="column.expression"
:isStorage="column.isStorage"
:formData="formData"
:precision="column.precision"
:thousands="column.thousands"
:isAmountChinese="column.isAmountChinese"
:roundType="column.roundType"
detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'location'">
<yunzhupaas-location v-model:value="record[column.dataIndex]" :enableLocationScope="column.enableLocationScope" detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="record[column.dataIndex]" detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="record[column.dataIndex]" detailed />
</template>
<template v-else-if="column.__config__?.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.dataIndex]"
:addonBefore="column.addonBefore"
:addonAfter="column.addonAfter"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
detailed />
</template>
<template v-else>
<p>{{ getValue(record[column.dataIndex]) }}</p>
</template>
</template>
<template #summary v-if="getItem.__config__.defaultValue.length && getItem.showSummary">
<a-table-summary fixed>
<a-table-summary-row>
<a-table-summary-cell :index="0">{{ t('component.table.summary') }}</a-table-summary-cell>
<a-table-summary-cell v-for="(item, index) in getColumnSum" :key="index" :index="index + 1" :align="getSummaryCellAlign(index)">
{{ item }}
</a-table-summary-cell>
</a-table-summary-row>
</a-table-summary>
</template>
</a-table>
</a-form-item>
</template>
<template v-if="getConfig.yunzhupaasKey === 'tableGrid'">
<table
class="table-grid-box"
:style="{
'--borderType': getItem.__config__.borderType,
'--borderColor': getItem.__config__.borderColor,
'--borderWidth': getItem.__config__.borderWidth + 'px',
}">
<tbody>
<tr v-for="(tr, index) in getConfig.children" :key="index">
<td
v-for="(td, i) in tr.__config__.children"
:key="i"
:colspan="td.__config__.colspan"
:rowspan="td.__config__.rowspan"
v-show="!td.__config__.merged"
:style="{
'--backgroundColor': td.__config__.backgroundColor,
}">
<a-row>
<Item
v-for="(childItem, childIndex) in td.__config__.children"
v-bind="getBindValue"
:key="childIndex"
:item="childItem"
@toDetail="toDetail" />
</a-row>
</td>
</tr>
</tbody>
</table>
</template>
</template>
</a-col>
</template>
<script lang="ts" setup>
import { computed, unref, reactive, toRefs, onMounted } from 'vue';
import { omit } from 'lodash-es';
import { thousandsFormat } from '@/utils/yunzhupaas';
import { useI18n } from '@/hooks/web/useI18n';
import { CaretRightOutlined } from '@ant-design/icons-vue';
import { buildUUID } from '@/utils/uuid';
import ExtraRelationInfo from '@/components/Yunzhupaas/RelationForm/src/ExtraRelationInfo.vue';
import { getDataChange } from '@/api/onlineDev/visualDev';
import { getDataInterfaceDataInfoByIds } from '@/api/systemData/dataInterface';
interface State {
outerActiveKey: number[];
innerActiveKey: string[];
extraData: any;
}
defineOptions({ name: 'Item' });
const props = defineProps({
item: { type: Object, required: true },
formConf: { type: Object, required: true },
formData: { type: Object },
loading: { type: Boolean, default: false },
});
const emit = defineEmits(['toDetail']);
const { t } = useI18n();
const state = reactive<State>({
outerActiveKey: [0],
innerActiveKey: [],
extraData: {},
});
const { outerActiveKey, innerActiveKey, extraData } = toRefs(state);
const getBindValue = computed(() => ({ ...omit(props, ['item']) }));
const getItem = computed(() => {
const item = props.item;
const config = item.__config__;
if (['groupTitle', 'divider', 'link', 'text'].includes(config.yunzhupaasKey)) {
if (item.contentI18nCode) item.content = t(item.contentI18nCode, item.content);
if (item.helpMessageI18nCode) item.helpMessage = t(item.helpMessageI18nCode, item.helpMessage);
}
if (config.yunzhupaasKey === 'button') {
if (item.buttonTextI18nCode) item.buttonText = t(item.buttonTextI18nCode, item.buttonText);
}
if (config.yunzhupaasKey === 'alert') {
if (item.titleI18nCode) item.title = t(item.titleI18nCode, item.title);
if (item.descriptionI18nCode) item.description = t(item.descriptionI18nCode, item.description);
if (item.closeTextI18nCode) item.closeText = t(item.closeTextI18nCode, item.closeText);
}
if (config.yunzhupaasKey === 'card') {
if (item.headerI18nCode) item.header = t(item.headerI18nCode, item.header);
}
return item;
});
const getConfig = computed(() => props.item.__config__);
const getDefaultValue = computed(() => {
let defaultValue = props.item.__config__.defaultValue;
if (unref(getConfig).yunzhupaasKey === 'table') {
defaultValue = defaultValue.map(o => ({ ...o, yunzhupaasId: buildUUID() }));
} else {
Array.isArray(defaultValue) && (defaultValue = defaultValue.join());
}
return defaultValue;
});
const getLabelCol = computed(() => {
const globalLabelWidth = props.formConf.labelWidth;
let labelCol = {};
if (props.formConf.labelPosition !== 'top' && unref(getConfig).showLabel) {
let labelWidth = (unref(getConfig).labelWidth || globalLabelWidth) + 'px';
if (!unref(getConfig).showLabel) labelWidth = '0px';
labelCol = { style: { width: labelWidth } };
}
return labelCol;
});
const getLabel = computed(() => (unref(getConfig).labelI18nCode ? t(unref(getConfig).labelI18nCode, unref(getConfig).label) : unref(getConfig).label));
const getTipLabel = computed(() =>
unref(getConfig).tipLabelI18nCode ? t(unref(getConfig).tipLabelI18nCode, unref(getConfig).tipLabel) : unref(getConfig).tipLabel,
);
const getColumns = computed(() => {
if (unref(getConfig).yunzhupaasKey !== 'table') return [];
const noColumn = {
width: 50,
title: t('component.table.index'),
dataIndex: 'index',
key: 'index',
align: 'center',
customRender: ({ index }) => index + 1,
fixed: 'left',
};
const list = unref(getConfig)
.children.filter(
o => !o.__config__.noShow && (!o.__config__.visibility || (Array.isArray(o.__config__.visibility) && o.__config__.visibility.includes('pc'))),
)
.map(o => ({
...o,
title: o.__config__?.labelI18nCode ? t(o.__config__?.labelI18nCode, o.__config__?.label) : o.__config__?.label,
tipLabel: o.__config__?.tipLabelI18nCode ? t(o.__config__?.tipLabelI18nCode, o.__config__?.tipLabel) : o.__config__?.tipLabel,
dataIndex: o.__vModel__,
width: o.__config__?.columnWidth || undefined,
align: o.__config__.tableAlign || 'left',
fixed: o.__config__.tableFixed == 'left' || o.__config__.tableFixed == 'right' ? o.__config__.tableFixed : false,
...(props.item.layoutType === 'list' ? { labelCol: getChildTableLabelCol(o.__config__) } : {}),
}));
let columnList = list;
if (props.item.layoutType === 'list') return columnList;
let complexHeaderList: any[] = props.item.__config__.complexHeaderList || [];
if (complexHeaderList.length) {
let childColumns: any[] = [];
let firstChildColumns: string[] = [];
for (let i = 0; i < complexHeaderList.length; i++) {
const e = complexHeaderList[i];
e.title = e.fullNameI18nCode ? t(e.fullNameI18nCode, e.fullName) : e.fullName;
e.align = e.align;
e.children = [];
e.yunzhupaasKey = 'complexHeader';
if (e.childColumns?.length) {
childColumns.push(...e.childColumns);
for (let k = 0; k < e.childColumns.length; k++) {
const item = e.childColumns[k];
for (let j = 0; j < list.length; j++) {
const o = list[j];
if (o.__vModel__ == item && o.__config__.tableFixed !== 'left' && o.__config__.tableFixed !== 'right') e.children.push({ ...o });
}
}
}
if (e.children.length) firstChildColumns.push(e.children[0].__vModel__);
}
complexHeaderList = complexHeaderList.filter(o => o.children.length);
let newList: any[] = [];
for (let i = 0; i < list.length; i++) {
const e = list[i];
if (!childColumns.includes(e.__vModel__) || e.__config__?.tableFixed === 'left' || e.__config__?.tableFixed === 'right') {
newList.push(e);
} else {
if (firstChildColumns.includes(e.__vModel__)) {
const item = complexHeaderList.find(o => o.childColumns.includes(e.__vModel__));
newList.push(item);
}
}
}
columnList = newList;
}
let columns = [noColumn, ...columnList];
const leftFixedList = columns.filter(o => o.fixed === 'left');
const rightFixedList = columns.filter(o => o.fixed === 'right');
const noFixedList = columns.filter(o => o.fixed !== 'left' && o.fixed !== 'right');
return [...leftFixedList, ...noFixedList, ...rightFixedList];
});
const getSummaryColumn = computed(() => {
let defaultColumns = unref(getColumns);
let columns: any[] = [];
for (let i = 0; i < defaultColumns.length; i++) {
const e = defaultColumns[i];
if (e.yunzhupaasKey === 'table' || e.yunzhupaasKey === 'complexHeader') {
if (e.children?.length) columns.push(...e.children);
} else {
columns.push(e);
}
if (e.fixed && e.children?.length) {
for (let j = 0; j < e.children.length; j++) {
e.children[j].fixed = e.fixed;
}
}
}
return columns.filter(o => o?.key != 'index' && o?.key != 'action');
});
const getColumnSum = computed(() => {
if (unref(getConfig).yunzhupaasKey !== 'table') return [];
const list = unref(getSummaryColumn);
const sums: any[] = [];
const isSummary = key => props.item.summaryField.includes(key);
const useThousands = key => list.some(o => o.__vModel__ === key && o.thousands);
const tableData = list.filter(o => !o.__config__.noShow && (!o.__config__.visibility || o.__config__.visibility.includes('pc')));
tableData.forEach((column, index) => {
let sumVal = unref(getConfig).defaultValue.reduce((sum, d) => sum + getCmpValOfRow(d, column.__vModel__), 0);
if (!isSummary(column.__vModel__)) sumVal = '';
sumVal = Number.isNaN(sumVal) ? '' : sumVal;
const realVal = sumVal && !Number.isInteger(sumVal) ? Number(sumVal).toFixed(2) : sumVal;
sums[index] = useThousands(column.__vModel__) ? thousandsFormat(realVal) : realVal.toString();
});
return sums;
});
function toDetail(item) {
emit('toDetail', item);
}
function toTableDetail(item, value) {
item.__config__.defaultValue = value;
emit('toDetail', item);
}
function getValue(value) {
return Array.isArray(value) ? value.join() : value;
}
function getCmpValOfRow(row, key) {
const isSummary = key => props.item.summaryField.includes(key);
if (!props.item.summaryField.length || !isSummary(key)) return 0;
const target = row[key];
if (!target) return 0;
const data = isNaN(target) ? 0 : Number(target);
return data;
}
function getSummaryCellAlign(index) {
if (!unref(getSummaryColumn).length) return;
return unref(getSummaryColumn)[index]?.align || 'left';
}
function getChildTableLabelCol(config) {
const globalLabelWidth = props.formConf.labelWidth;
let labelCol = {};
if (props.formConf.labelPosition !== 'top' && config.showLabel) {
let labelWidth = (config.labelWidth || globalLabelWidth) + 'px';
if (!config.showLabel) labelWidth = '0px';
labelCol = { style: { width: labelWidth } };
}
return labelCol;
}
// 平铺布局时设置默认展开
function setActiveKey() {
if (unref(getConfig).yunzhupaasKey !== 'table' || props.item.layoutType !== 'list') return;
state.outerActiveKey = [0];
state.innerActiveKey = [];
if (!props.item.defaultExpandAll) return;
state.innerActiveKey = ['summary'];
if (!unref(getDefaultValue).length) return;
for (let i = 0; i < unref(getDefaultValue).length; i++) {
state.innerActiveKey.push(unref(getDefaultValue)[i].yunzhupaasId);
}
}
function getParamList() {
let templateJson: any[] = unref(getItem).templateJson;
if (!templateJson || !templateJson.length || !props.formData) return templateJson;
for (let i = 0; i < templateJson.length; i++) {
if (templateJson[i].relationField && templateJson[i].sourceType == 1) {
templateJson[i].defaultValue = props.formData[templateJson[i].relationField + '_yunzhupaasId'] || '';
}
}
return templateJson;
}
// 弹窗选择/关联表单获取额外关联信息
function getExtraRelationInfo() {
if (!['relationForm', 'popupSelect'].includes(unref(getConfig).yunzhupaasKey) || !unref(getDefaultValue)) return;
if (unref(getConfig).yunzhupaasKey === 'relationForm') {
getDataChange(unref(getItem).modelId, { id: unref(getDefaultValue), propsValue: unref(getItem).propsValue }).then(res => {
if (!res.data || !res.data.data) return;
const data = JSON.parse(res.data.data);
state.extraData = data;
});
return;
}
if (unref(getConfig).yunzhupaasKey === 'popupSelect') {
const paramList = getParamList();
const query = {
ids: [unref(getDefaultValue)],
interfaceId: unref(getItem).interfaceId,
propsValue: unref(getItem).propsValue,
relationField: unref(getItem).relationField,
paramList,
};
getDataInterfaceDataInfoByIds(unref(getItem).interfaceId, query).then(res => {
const data = res.data && res.data.length ? res.data[0] : {};
state.extraData = data;
});
}
}
onMounted(() => {
setActiveKey();
getExtraRelationInfo();
});
</script>

View File

@@ -0,0 +1,40 @@
<template>
<a-row :class="formConf.formStyle ? formConf.formStyle + ' word-form-detail' : ''">
<a-form
class="w-full"
ref="formElRef"
:name="getFormName"
:colon="formConf.colon"
:size="formConf.size"
:disabled="formConf.disabled"
:labelCol="getLabelCol"
:layout="formConf.labelPosition === 'top' ? 'vertical' : 'horizontal'"
:labelAlign="formConf.labelPosition === 'right' ? 'right' : 'left'">
<a-row :gutter="formConf.formStyle ? 0 : formConf.gutter">
<Item v-for="(item, index) in formConf.fields" :key="index" :item="item" v-bind="getBindValue" @toDetail="toDetail" />
</a-row>
</a-form>
</a-row>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { buildUUID } from '@/utils/uuid';
import Item from './Item.vue';
const props = defineProps({
formConf: { type: Object, required: true },
relationData: { type: Object, default: () => {} },
formData: { type: Object },
loading: { type: Boolean, default: false },
});
const emit = defineEmits(['toDetail']);
const getFormName = computed(() => `form-${buildUUID()}`);
const getLabelCol = computed(() => ({ style: { width: props.formConf.labelWidth + 'px' } }));
const getBindValue = computed(() => ({ ...props }));
function toDetail(data) {
emit('toDetail', data);
}
</script>

View File

@@ -0,0 +1,445 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :title="title" destroyOnClose :closeFunc="onClose" class="full-popup">
<template #insertToolbar>
<a-button
class="ml-10px"
v-for="item in state.customBtns"
:key="item.value"
type="primary"
:preIcon="item.actionConfig?.btnIcon"
v-if="!loading"
@click="customBtnsHandle(item)">
{{ item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label }}
</a-button>
<a-button class="ml-10px" type="primary" @click="handlePrint" v-if="formConf.hasPrintBtn && formConf.printId">{{ getPrintText }}</a-button>
</template>
<div class="yunzhupaas-common-form-wrapper">
<div class="yunzhupaas-common-form-wrapper__main" :style="{ margin: '0 auto', width: formConf.fullScreenWidth || '100%' }">
<template v-if="!loading && extraList.length && !hideExtra">
<a-tabs v-model:activeKey="extraActiveKey" class="yunzhupaas-content-wrapper-tabs" destroyInactiveTabPane @change="onTabChange">
<a-tab-pane v-for="(item, index) in extraList" :key="index" :tab="item.fullName"></a-tab-pane>
</a-tabs>
<div class="yunzhupaas-content-detail-extra">
<Parser
class="p-10px !pt-0px"
ref="parserRef"
:formConf="formConf"
:formData="formData"
@toDetail="toDetail"
:key="key"
v-if="extraActiveKey == 0" />
<div class="h-full" v-loading="extraLoading" v-else>
<ExtraList
ref="extraListRef"
:config="extraList[extraActiveKey]"
:detailFormData="formData"
:key="extraKey"
@openDetail="handleOpenDetail"
@openForm="handleOpenForm"
v-if="extraList[extraActiveKey]?.extraConfig && !extraLoading" />
<yunzhupaas-empty class="extra-empty" v-if="!extraList[extraActiveKey]?.extraConfig && !extraLoading" />
</div>
</div>
</template>
<Parser
class="p-10px"
ref="parserRef"
:formConf="formConf"
:formData="formData"
@toDetail="toDetail"
:key="key"
v-if="!loading && (!extraList.length || hideExtra)" />
</div>
<FormExtraPanel v-bind="getFormExtraBind" v-if="state.dataForm.id && formConf.dataLog" />
</div>
</BasicPopup>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="formConf.generalWidth" :minHeight="100" :showOkBtn="false" :closeFunc="onClose">
<template #insertFooter>
<a-button
class="ml-10px"
v-for="item in state.customBtns"
:key="item.value"
type="primary"
:preIcon="item.actionConfig?.btnIcon"
v-if="!loading"
@click="customBtnsHandle(item)">
{{ item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label }}
</a-button>
<a-button class="ml-10px" type="primary" @click="handlePrint" v-if="formConf.hasPrintBtn && formConf.printId">{{ getPrintText }}</a-button>
</template>
<Parser ref="parserRef" :formConf="formConf" :formData="formData" @toDetail="toDetail" :key="key" v-if="!loading" />
</BasicModal>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" :title="title" :width="formConf.drawerWidth" showFooter :showOkBtn="false" :closeFunc="onClose">
<template #insertFooter>
<a-button
class="ml-10px"
v-for="item in state.customBtns"
:key="item.value"
type="primary"
:preIcon="item.actionConfig?.btnIcon"
v-if="!loading"
@click="customBtnsHandle(item)">
{{ item.labelI18nCode ? t(item.labelI18nCode, item.label) : item.label }}
</a-button>
<a-button class="ml-10px" type="primary" @click="handlePrint" v-if="formConf.hasPrintBtn && formConf.printId">{{ getPrintText }}</a-button>
</template>
<div class="p-10px">
<Parser ref="parserRef" :formConf="formConf" :formData="formData" @toDetail="toDetail" :key="key" v-if="!loading" />
</div>
</BasicDrawer>
<Detail v-if="detailVisible" ref="detailRef" @close="state.detailVisible = false" />
<Form v-if="formVisible" ref="formRef" @reload="reloadTable" />
<PrintSelect @register="registerPrintSelect" @change="handleShowBrowse" />
<PrintBrowse @register="registerPrintBrowse" />
<CustomForm ref="customFormRef" />
</template>
<script lang="ts" setup>
import { getDataChange, getConfigData, getConfigDataByMenuId, getModelInfo, launchFlow } from '@/api/onlineDev/visualDev';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { reactive, toRefs, nextTick, ref, computed } from 'vue';
import { BasicPopup, usePopup } from '@/components/Popup';
import { BasicModal, useModal } from '@/components/Modal';
import { BasicDrawer, useDrawer } from '@/components/Drawer';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import { useGeneratorStore } from '@/store/modules/generator';
import Parser from './Parser.vue';
import Form from '../Form.vue';
import PrintSelect from '@/components/PrintDesign/printSelect/index.vue';
import PrintBrowse from '@/components/PrintDesign/printBrowse/index.vue';
import { cloneDeep } from 'lodash-es';
import { getScriptFunc, onlineUtils, getParamList, getLaunchFlowParamList } from '@/utils/yunzhupaas';
import CustomForm from '../CustomForm.vue';
import FormExtraPanel from '@/components/FormExtraPanel/index.vue';
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
interface State {
formConf: any;
formData: any;
config: any;
loading: boolean;
key: number;
dataForm: any;
formOperates: any[];
title: string;
detailVisible: boolean;
formVisible: boolean;
customBtns: any[];
extraList: any[];
extraActiveKey: number;
extraConfig: any;
extraLoading: boolean;
extraKey: number;
hideExtra: boolean;
}
defineOptions({ name: 'Detail' });
const ExtraList = createAsyncComponent(() => import('./ExtraList.vue'));
const emit = defineEmits(['close']);
const userStore = useUserStore();
const generatorStore = useGeneratorStore();
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const [registerPopup, { openPopup, closePopup, setPopupProps }] = usePopup();
const [registerModal, { openModal, closeModal, setModalProps }] = useModal();
const [registerDrawer, { openDrawer, closeDrawer, setDrawerProps }] = useDrawer();
const [registerPrintSelect, { openModal: openPrintSelect }] = useModal();
const [registerPrintBrowse, { openModal: openPrintBrowse }] = useModal();
const parserRef = ref<any>(null);
const detailRef = ref<any>(null);
const customFormRef = ref<any>(null);
const formRef = ref<any>(null);
const extraListRef = ref<any>(null);
const state = reactive<State>({
formConf: {},
formData: {},
config: {},
loading: false,
key: +new Date(),
dataForm: {
id: '',
data: '',
},
formOperates: [],
title: t('common.detailText'),
detailVisible: false,
formVisible: false,
customBtns: [],
extraList: [],
extraActiveKey: 0,
extraConfig: {},
extraLoading: false,
extraKey: 0,
hideExtra: false,
});
const { title, formConf, formData, key, loading, detailVisible, formVisible, extraList, extraActiveKey, extraLoading, extraKey, hideExtra } = toRefs(state);
const getPrintText = computed(() => {
const text = state.formConf.printButtonTextI18nCode
? t(state.formConf.printButtonTextI18nCode, state.formConf.printButtonText)
: state.formConf.printButtonText;
return text || t('common.printText');
});
const getFormExtraBind = computed(() => ({ showLog: state.formConf.dataLog, modelId: state.config.modelId, formDataId: state.config.id }));
defineExpose({ init });
function fillFormData(form, data) {
const loop = (list, parent?) => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.__vModel__) {
if (item.__config__.yunzhupaasKey === 'relationForm' || item.__config__.yunzhupaasKey === 'popupSelect') {
item.__config__.defaultValue = data[item.__vModel__ + '_id'];
item.name = data[item.__vModel__] || '';
} else {
const val = data.hasOwnProperty(item.__vModel__) ? data[item.__vModel__] : item.__config__.defaultValue;
item.__config__.defaultValue = val;
}
if (!state.config.isDataManage && state.config.useFormPermission) {
let id = item.__config__.isSubTable ? parent.__vModel__ + '-' + item.__vModel__ : item.__vModel__;
let noShow = true;
if (state.formOperates && state.formOperates.length) {
noShow = !state.formOperates.some(o => o.enCode === id);
}
noShow = item.__config__.noShow ? item.__config__.noShow : noShow;
item.__config__.noShow = noShow;
}
} else {
if (['relationFormAttr', 'popupAttr'].includes(item.__config__.yunzhupaasKey)) {
item.__config__.defaultValue = data[item.relationField.split('_yunzhupaasTable_')[0] + '_' + item.showField];
}
}
if (item.__config__ && item.__config__.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children, item);
}
}
};
loop(form.fields);
}
function init(data) {
state.loading = true;
state.config = data;
state.formConf = cloneDeep(data.formConf);
state.customBtns = (state.formConf.customBtns || []).reverse();
state.dataForm.id = data.id;
state.extraActiveKey = 0;
state.extraConfig = {};
state.hideExtra = data.hideExtra || false;
getFormOperates();
getExtraList();
openForm();
nextTick(() => {
setTimeout(initData, 0);
});
}
function initData() {
changeLoading(true);
state.loading = true;
if (state.config.id) {
const extra = { modelId: state.config.modelId, id: state.config.id, type: 2 };
generatorStore.setDynamicModelExtra(extra);
getInfo(state.config.id, state.config.propsValue);
} else {
closeForm();
}
}
function getInfo(id, propsValue) {
let query: any = {
id: id,
menuId: state.config.menuId,
};
if (propsValue) query = { ...query, propsValue };
getDataChange(state.config.modelId, query).then(res => {
state.dataForm = res.data || {};
if (!state.dataForm.data) return;
state.formData = JSON.parse(state.dataForm.data);
fillFormData(state.formConf, state.formData);
initRelationForm(state.formConf.fields);
nextTick(() => {
state.loading = false;
state.key = +new Date();
changeLoading(false);
});
});
}
function initRelationForm(componentList) {
componentList.forEach(cur => {
const config = cur.__config__;
if (config.yunzhupaasKey == 'relationFormAttr' || config.yunzhupaasKey == 'popupAttr') {
const relationKey = cur.relationField.split('_yunzhupaasTable_')[0];
componentList.forEach(item => {
const noVisibility = Array.isArray(item.__config__.visibility) && !item.__config__.visibility.includes('pc');
if (relationKey == item.__vModel__ && (noVisibility || !!item.__config__.noShow) && !cur.__vModel__) {
cur.__config__.noShow = true;
}
});
}
if (cur.__config__.children && cur.__config__.children.length) initRelationForm(cur.__config__.children);
});
}
function getFormOperates() {
if (state.config.isPreview || state.config.isDataManage || !state.config.useFormPermission) return;
const permissionList = userStore.getPermissionList;
const modelId = state.config.menuId;
const list = permissionList.filter(o => o.modelId === modelId);
state.formOperates = list[0] && list[0].form ? list[0].form : [];
}
function toDetail(item) {
if (!item.__config__.defaultValue) return;
getConfigData(item.modelId).then(res => {
if (!res.data) return;
if (!res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = state.formData.popupType;
formConf.customBtns = [];
formConf.hasPrintBtn = false;
const data = {
id: item.__config__.defaultValue,
formConf,
modelId: item.modelId,
propsValue: item.propsValue,
};
state.detailVisible = true;
nextTick(() => {
detailRef.value?.init(data);
});
});
}
function handlePrint() {
if (state.config.isPreview) return createMessage.warning('功能预览不支持打印');
if (!state.formConf.printId?.length) return createMessage.error('未配置打印模板');
if (state.formConf.printId?.length === 1) return handleShowBrowse(state.formConf.printId[0]);
openPrintSelect(true, state.formConf.printId);
}
function handleShowBrowse(id) {
openPrintBrowse(true, { id, formInfo: [{ formId: state.dataForm.id }] });
}
function openForm() {
if (state.formConf.popupType === 'fullScreen') return openPopup();
if (state.formConf.popupType === 'drawer') return openDrawer();
openModal();
}
function closeForm() {
if (state.formConf.popupType === 'fullScreen') return closePopup();
if (state.formConf.popupType === 'drawer') return closeDrawer();
closeModal();
}
function setFormProps(data) {
if (state.formConf.popupType === 'fullScreen') return setPopupProps(data);
if (state.formConf.popupType === 'drawer') return setDrawerProps(data);
setModalProps(data);
}
function changeLoading(loading) {
setFormProps({ loading });
}
async function onClose() {
emit('close');
return true;
}
// 自定义按钮点击事件
function customBtnsHandle(item) {
if (item.actionConfig.btnType == 1) handlePopup(item.actionConfig);
if (item.actionConfig.btnType == 2) handleScriptFunc(item.actionConfig);
if (item.actionConfig.btnType == 3) handleInterface(item.actionConfig);
if (item.actionConfig.btnType == 4) handleLaunchFlow(item);
}
function handlePopup(item) {
const data = {
...item,
recordModelId: state.config.modelId,
record: state.formData,
};
customFormRef.value?.init(data);
}
function handleScriptFunc(item) {
const parameter = { data: state.formData, onlineUtils };
const func: any = getScriptFunc(item.func);
if (!func) return;
func(parameter);
}
function handleInterface(item) {
const handlerData = () => {
getModelInfo(state.config.modelId, state.config.id).then(res => {
const dataForm = res.data || {};
if (!dataForm.data) return;
const data = { ...JSON.parse(dataForm.data), id: state.config.id };
handlerInterface(data);
});
};
const handlerInterface = data => {
const query = { paramList: getParamList(item.templateJson, { ...data, id: state.config.id }) || [] };
getDataInterfaceRes(item.interfaceId, query).then(res => {
createMessage.success(res.msg);
});
};
if (!item.useConfirm) return handlerData();
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: item.confirmTitle || '确认执行此操作?',
onOk: () => {
handlerData();
},
});
}
function handleLaunchFlow(item) {
const launchFlowCfg = cloneDeep(item.actionConfig.launchFlow);
const query = {
template: launchFlowCfg.flowId,
btnCode: item.value,
currentUser: launchFlowCfg.currentUser,
customUser: launchFlowCfg.customUser,
initiator: launchFlowCfg.initiator,
dataList: [getLaunchFlowParamList(launchFlowCfg.transferList, state.formData)],
};
launchFlow(state.config.modelId, query).then(res => {
createMessage.success(res.msg);
});
}
function getExtraList() {
state.extraList = state.formConf.detailExtraList?.length ? [{ fullName: state.config.title, id: 0 }, ...state.formConf.detailExtraList] : [];
}
function onTabChange(index) {
if (state.extraList[index]?.extraConfig) return (state.extraKey = +new Date());
state.extraLoading = true;
setTimeout(() => {
getConfig(state.extraList[index]?.targetFormId, index);
}, 200);
}
function getConfig(menuId, index) {
if (!menuId) return (state.extraLoading = false);
getConfigDataByMenuId({ menuId, systemId: userStore.getUserInfo?.systemId })
.then(res => {
if (res.code !== 200 || !res.data) {
state.extraList[index].extraConfig = '';
state.extraLoading = false;
state.extraKey = +new Date();
return;
}
state.extraList[index].extraConfig = res.data;
state.extraLoading = false;
state.extraKey = +new Date();
})
.catch(() => {
state.extraLoading = false;
state.extraKey = +new Date();
});
}
function handleOpenDetail(data) {
state.detailVisible = true;
nextTick(() => {
detailRef.value?.init(data);
});
}
function handleOpenForm(data) {
state.formVisible = true;
nextTick(() => {
formRef.value?.init(data);
});
}
function reloadTable() {
extraListRef.value?.reload();
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
<template>
<div class="yunzhupaas-content-wrapper" v-loading="loading">
<template v-if="!ajaxing">
<template v-if="portalId">
<PortalLayout :layout="layout" :enabledLock="enabledLock" v-if="type === 0" @layoutUpdatedEvent="layoutUpdatedEvent" />
<div class="custom-page" v-if="type === 1">
<component :is="currentView" v-if="linkType === 0" />
<embed :src="url" width="100%" height="100%" type="text/html" v-if="linkType === 1" />
</div>
</template>
<div class="portal-layout-nodata" v-else>
<yunzhupaas-empty :image="emptyImage" />
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { onMounted, toRefs, onUnmounted } from 'vue';
import { useRoute } from 'vue-router';
import PortalLayout from '@/components/VisualPortal/Portal/Layout/index.vue';
import { usePortal } from '@/views/basic/home/hooks/usePortal';
import emptyImage from '@/assets/images/dashboard-nodata.png';
defineOptions({ name: 'dynamicPortal' });
defineEmits(['register']);
const { state, initData, clearAutoRefresh, layoutUpdatedEvent } = usePortal();
const { loading, layout, type, linkType, currentView, url, ajaxing, portalId, enabledLock } = toRefs(state);
function init() {
const route = useRoute();
state.portalId = (route.meta.relationId as string) || '';
if (!state.portalId) return;
initData();
}
onMounted(() => init());
onUnmounted(() => clearAutoRefresh());
</script>
<style lang="less">
@import '@/components/VisualPortal/style/index.less';
</style>

View File

@@ -0,0 +1,196 @@
<template>
<div class="yunzhupaas-content-wrapper yunzhupaas-dynamicReport-wrapper" v-loading="pageLoading">
<div class="tool-wrap" v-if="getHasBtn">
<a-tooltip title="打印" v-if="allowPrint">
<a-button type="text" :disabled="pageLoading" class="action-bar-btn" @click="handlePrint">
<i class="icon-ym icon-ym-printExample" />
</a-button>
</a-tooltip>
<a-tooltip title="导出" v-if="allowExport">
<a-button type="text" :disabled="pageLoading" class="action-bar-btn" @click="handleDownload">
<i class="icon-ym icon-ym-btn-export1" />
</a-button>
</a-tooltip>
</div>
<div class="query-wrap" v-if="searchSchemas?.length">
<BasicForm @register="registerSearchForm" :schemas="searchSchemas" @submit="handleSearchSubmit" @reset="searchFormSubmit" class="search-form">
</BasicForm>
</div>
<div class="content-main">
<YunzhupaasUniver ref="yunzhupaasUniverRef" :key="yunzhupaasUniverKey" />
</div>
</div>
<ReportPrint @register="registerReportPrint" />
</template>
<script lang="ts" setup>
import { reactive, onMounted, onUnmounted, toRefs, ref, unref, nextTick, computed } from 'vue';
import { useRoute } from 'vue-router';
import { getPreviewTemplate, exportFileExcel } from '@/api/onlineDev/report';
import { BasicForm, useForm } from '@/components/Form';
import YunzhupaasUniver from 'yunzhupaas-univer';
import { useReport } from '@/components/Report/src/hooks/useReport';
import { ReportPrint } from '@/components/Report';
import { downloadByUrlReport } from '@/utils/file/download';
import { useModal } from '@/components/Modal';
import { getFloatUrl } from '@/components/Report/src/helper';
import { useGlobSetting } from '@/hooks/setting';
defineOptions({ name: 'dynamicReport' });
interface State {
id: string;
reportData: any;
pageLoading: boolean;
yunzhupaasUniverAPI: any;
allowExport: number;
allowPrint: number;
sheetId: string;
yunzhupaasUniverKey: number;
queryList: any[];
}
const yunzhupaasUniverRef = ref();
const state = reactive<State>({
id: '',
reportData: {},
pageLoading: false,
yunzhupaasUniverAPI: null,
allowExport: 0,
allowPrint: 0,
sheetId: '',
yunzhupaasUniverKey: 0,
queryList: [],
});
const { pageLoading, allowExport, allowPrint, yunzhupaasUniverKey } = toRefs(state);
const [registerReportPrint, { openModal: openPrintModal }] = useModal();
const [registerSearchForm, { updateSchema, submit: searchFormSubmit, getFieldsValue }] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
});
const formApi = { updateSchema, getFieldsValue };
const { getRealEchart, getSearchSchema, searchSchemas } = useReport(formApi);
const globSetting = useGlobSetting();
const getHasBtn = computed(() => {
if (state.allowExport || state.allowPrint) return true;
return false;
});
// 初始化
function init() {
const route = useRoute();
state.id = route?.meta?.relationId as string;
if (!state.id) return;
state.pageLoading = true;
initData();
}
function initData(data = {}) {
state.pageLoading = true;
getPreviewTemplate(state.id, data).then(res => {
state.reportData = res.data;
state.allowExport = res.data.allowExport || 0;
state.allowPrint = res.data.allowPrint || 0;
state.queryList = res.data.queryList ? JSON.parse(res.data.queryList) : [];
const snapshot = res.data.snapshot ? JSON.parse(res.data.snapshot) : null;
const cells = res.data.cells ? JSON.parse(res.data.cells) : {};
const chartData = res.data.chartData ? JSON.parse(res.data.chartData) : [];
const floatEcharts = getRealEchart(cells.floatEcharts ?? null, chartData);
let floatImages = cells.floatImages || {};
let item = getFloatUrl(snapshot.resources, globSetting.reportApiUrl, floatImages);
snapshot.resources = item.list;
floatImages = item?.floatImages || {};
getSearchSchema(state.queryList, state.sheetId || '');
state.yunzhupaasUniverKey = +new Date();
nextTick(() => {
handleCreate(snapshot, floatEcharts, floatImages);
});
state.pageLoading = false;
});
}
// 创建报表实例
function handleCreate(snapshot, floatEcharts, floatImages) {
const res = unref(yunzhupaasUniverRef)?.handleCreateDesignUnit({
mode: 'preview',
snapshot,
floatEcharts,
floatImages,
uiHeader: false,
uiContextMenu: false,
workbookReadonly: true,
defaultActiveSheetId: state.sheetId || '',
loading: true,
});
state.yunzhupaasUniverAPI = res ? res?.yunzhupaasUniverAPI : null;
onReportCommandExecuted();
}
function onReportCommandExecuted() {
state.yunzhupaasUniverAPI?.onCommandExecuted((command: any) => {
const { id: commandId } = command ?? {};
// 切换sheet
if (commandId === 'sheet.operation.set-worksheet-active') {
state.sheetId = command.params.subUnitId;
getSearchSchema(state.queryList, state.sheetId);
}
});
}
// 销毁示例
function handleDisposeUnit() {
unref(yunzhupaasUniverRef)?.handleDisposeUnit();
}
function handleSearchSubmit(data) {
initData({ sheetId: state.sheetId, ...data });
}
// 打印
function handlePrint() {
const { snapshot } = unref(yunzhupaasUniverRef)?.getPreviewWorkbookData();
const sheetId = state.yunzhupaasUniverAPI.getActiveWorkbook()?.getActiveSheet()?.getSheetId();
const activeWorksheetId = unref(yunzhupaasUniverRef)?.getActiveWorksheetId();
const { xSplit = 0, ySplit = 0 } = snapshot?.sheets?.[activeWorksheetId]?.freeze ?? {};
const hasXFreeze = xSplit > 0;
const hasYFreeze = ySplit > 0;
openPrintModal(true, { id: state.reportData.versionId, fullName: state.reportData.fullName, sheetId, snapshot, hasXFreeze, hasYFreeze });
}
// 导出
function handleDownload() {
const { snapshot } = unref(yunzhupaasUniverRef)?.getPreviewWorkbookData();
const sheetId = state.yunzhupaasUniverAPI.getActiveWorkbook()?.getActiveSheet()?.getSheetId();
const data = unref(searchSchemas).length ? getFieldsValue() : {};
const query = { ...data, sheetId, fullName: state.reportData.fullName, snapshot: snapshot ? JSON.stringify(snapshot) : null };
exportFileExcel(state.reportData.versionId, query).then(res => {
downloadByUrlReport({ url: res.data.url });
});
}
onMounted(() => {
init();
});
onUnmounted(() => {
handleDisposeUnit();
});
</script>
<style lang="less">
.yunzhupaas-dynamicReport-wrapper {
background-color: @component-background;
display: flex;
flex-direction: column;
.tool-wrap {
height: 53px;
padding: 10px;
border-bottom: 1px solid @border-color-base1;
}
.query-wrap {
padding: 10px 10px 0;
border-bottom: 1px solid #f0f0f0;
}
.content-main {
flex: 1;
overflow: hidden;
position: relative;
}
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<div class="yunzhupaas-content-wrapper yunzhupaas-content-wrapper-form">
<iframe width="100%" height="100%" frameborder="0" scrolling="yes" :src="url"></iframe>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useTabs } from '@/hooks/web/useTabs';
import { decodeByBase64 } from '@/utils/cipher';
defineOptions({ name: 'externalLink' });
const route = useRoute();
const { setTitle } = useTabs();
const url = ref('');
onMounted(() => {
const { query } = route;
if (!query.href) return;
const href = decodeByBase64(query.href as string);
url.value = href;
if (query.name) setTitle(query.name as string);
});
</script>

View File

@@ -0,0 +1,217 @@
<template>
<div class="yunzhupaas-content-wrapper short-link-wrapper">
<div class="short-link-lock-wrapper" v-show="formPassUse">
<div class="short-link-lock-form">
<a-input-group compact class="enter-y">
<a-input-password v-model:value="password" :placeholder="t('views.dynamicModel.passwordPlaceholder')" @keyup.enter="unLock()" />
<a-button :loading="btnLoading" @click="unLock()">
<template #icon><unlock-outlined /></template>
</a-button>
</a-input-group>
</div>
</div>
<div class="short-link-main" v-if="!formPassUse">
<a-popover placement="bottomRight">
<template #content>
<p class="shortLink-tip">{{ t('views.dynamicModel.scanAndShare') }}</p>
<QrCode :value="state.formLink" :width="154" :options="{ margin: 1 }" class="my-5px" />
</template>
<i class="ym-custom ym-custom-qrcode icon-qrcode"></i>
</a-popover>
<div class="short-link-header">
{{ config.fullName }}
</div>
<div class="short-link-content short-link-form">
<Parser ref="parserRef" :formConf="formConf" :isShortLink="true" @submit="submitForm" :key="key" v-if="!loading" />
</div>
<div class="short-link-footer">
<a-button type="primary" @click="handleSubmit" :loading="btnLoading">{{ getOkText }}</a-button>
<a-button type="warning" class="ml-10px" @click="handleReset">{{ t('common.resetText') }}</a-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { getConfig, createModel, checkPwd } from '@/api/onlineDev/shortLink';
import { reactive, toRefs, nextTick, ref, unref, onMounted, computed } from 'vue';
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { QrCode } from '@/components/Qrcode/index';
import { UnlockOutlined } from '@ant-design/icons-vue';
import { encryptByMd5 } from '@/utils/cipher';
import dayjs from 'dayjs';
import { getDateTimeUnit } from '@/utils/yunzhupaas';
interface State {
formLink: string;
formConf: any;
config: any;
loading: boolean;
btnLoading: boolean;
key: number;
shortLinkId: string;
formPassUse: number;
password: string;
}
const props = defineProps(['config', 'modelId', 'isPreview']);
const { createMessage } = useMessage();
const { t } = useI18n();
const parserRef = ref<any>(null);
const state = reactive<State>({
formLink: '',
formConf: {},
config: {},
loading: false,
btnLoading: false,
key: +new Date(),
shortLinkId: '',
formPassUse: -1,
password: '',
});
const { formConf, key, loading, config, btnLoading, formPassUse, password } = toRefs(state);
const Parser = createAsyncComponent(() => import('@/components/FormGenerator/src/components/Parser.vue'));
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',
'iframe',
'steps',
'stepItem',
];
const selectFieldsList = ['radio', 'checkbox', 'select', 'treeSelect', 'cascader'];
const getOkText = computed(() => {
const text = state.formConf.confirmButtonTextI18nCode
? t(state.formConf.confirmButtonTextI18nCode, state.formConf.confirmButtonText)
: state.formConf.confirmButtonText;
return text || t('common.okText');
});
function init() {
state.config = props.config;
state.formConf = state.config.formData ? JSON.parse(state.config.formData) : {};
state.formConf.fields = getRealFields(state.formConf.fields);
fillFormData(state.formConf, {});
nextTick(() => {
state.loading = false;
state.key = +new Date();
});
}
function validFields(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;
}
function getRealFields(list) {
let newList = list.filter(item => validFields(item));
newList.forEach(o => o.__config__?.children && Array.isArray(o.__config__.children) && (o.__config__.children = getRealFields(o.__config__.children)));
return newList;
}
function fillFormData(form, data) {
const currDate = new Date();
const loop = list => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.__vModel__) {
if (item.__config__.defaultCurrent) {
if (item.__config__.yunzhupaasKey === 'datePicker') {
item.__config__.defaultValue = dayjs(currDate).startOf(getDateTimeUnit(item.format)).valueOf();
}
if (item.__config__.yunzhupaasKey === 'timePicker') {
item.__config__.defaultValue = dayjs(currDate).format(item.format || 'HH:mm:ss');
}
}
}
if (item.__config__ && item.__config__.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children);
}
}
};
loop(form.fields);
form.formData = data;
}
function submitForm(data, callback) {
if (!data) return;
state.btnLoading = true;
const dataForm = { data: JSON.stringify(data) };
createModel(props.modelId, dataForm, state.config.encryption)
.then(res => {
createMessage.success(res.msg);
if (callback && typeof callback === 'function') callback();
state.btnLoading = false;
handleReset();
})
.catch(() => {
state.btnLoading = false;
});
}
function handleReset() {
getParser().handleReset();
}
function handleSubmit() {
if (props.isPreview) return createMessage.warning('功能预览不支持数据保存');
getParser().handleSubmit();
}
function getParser() {
const parser = unref(parserRef);
if (!parser) throw new Error('parser is null!');
return parser;
}
function unLock() {
if (!state.password) return createMessage.error(t('views.dynamicModel.passwordPlaceholder'));
state.btnLoading = true;
const query = {
id: state.shortLinkId,
type: 0,
encryption: props.config.encryption,
password: encryptByMd5(state.password),
};
checkPwd(query)
.then(() => {
state.btnLoading = false;
state.formPassUse = 0;
init();
})
.catch(() => {
state.btnLoading = false;
});
}
onMounted(() => {
state.loading = true;
getConfig(props.modelId, props.config.encryption).then(res => {
state.formLink = res.data.formLink || '';
state.shortLinkId = res.data.id || '';
state.formPassUse = res.data.formPassUse || 0;
if (state.formPassUse) return;
init();
});
});
</script>

View File

@@ -0,0 +1,66 @@
<template>
<component :is="currentView" :config="config" :modelId="modelId" :isPreview="isPreview" v-if="showPage" />
</template>
<script lang="ts" setup>
import { reactive, onMounted, toRefs, markRaw } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getConfigData } from '@/api/onlineDev/shortLink';
import { useMessage } from '@/hooks/web/useMessage';
import { useTabs } from '@/hooks/web/useTabs';
import Form from './form/index.vue';
import List from './list/index.vue';
import { AesEncryption } from '@/utils/cipher';
interface State {
currentView: any;
showPage: boolean;
isPreview: boolean;
modelId: string;
config: any;
encryption: string;
}
defineOptions({ name: 'formShortLink' });
const { createMessage } = useMessage();
const { close } = useTabs();
const state = reactive<State>({
currentView: '',
showPage: false,
isPreview: false,
modelId: '',
config: {},
encryption: '',
});
const { currentView, showPage, isPreview, modelId, config } = toRefs(state);
const router = useRouter();
function init() {
const route = useRoute();
if (!route.query.encryption) return;
state.encryption = route.query.encryption as string;
const aesEncryption = new AesEncryption({ useHex: true });
const configStr = aesEncryption.decryptByAES(state.encryption);
if (!configStr) return;
const config = JSON.parse(configStr);
state.modelId = config.modelId;
if (!state.modelId) return;
getConfig(config.type);
}
function getConfig(type) {
getConfigData(state.modelId, state.encryption).then(res => {
if (res.code !== 200 || !res.data) {
close();
router.replace('/404');
createMessage.error(res.msg || '请求出错,请重试');
return;
}
state.config = { ...res.data, encryption: state.encryption };
state.currentView = type == 'form' ? markRaw(Form) : markRaw(List);
state.showPage = true;
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,167 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :title="title" destroyOnClose :closeFunc="onClose">
<div class="p-10px" :style="{ margin: '0 auto', width: formConf.fullScreenWidth || '100%' }">
<Parser ref="parserRef" :formConf="formConf" :formData="formData" :relationData="relationData" :key="key" v-if="!loading" />
</div>
</BasicPopup>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="formConf.generalWidth" :minHeight="100" :showOkBtn="false" :closeFunc="onClose">
<Parser ref="parserRef" :formConf="formConf" :formData="formData" :relationData="relationData" :key="key" v-if="!loading" />
</BasicModal>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" :title="title" :width="formConf.drawerWidth" showFooter :showOkBtn="false" :closeFunc="onClose">
<div class="p-10px">
<Parser ref="parserRef" :formConf="formConf" :formData="formData" :relationData="relationData" :key="key" v-if="!loading" />
</div>
</BasicDrawer>
<Detail v-if="detailVisible" ref="detailRef" @close="state.detailVisible = false" />
</template>
<script lang="ts" setup>
import { getDataChange } from '@/api/onlineDev/shortLink';
import { reactive, toRefs, nextTick, ref } from 'vue';
import { BasicPopup, usePopup } from '@/components/Popup';
import { BasicModal, useModal } from '@/components/Modal';
import { BasicDrawer, useDrawer } from '@/components/Drawer';
import Parser from '../../../dynamicModel/list/detail/Parser.vue';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '@/hooks/web/useI18n';
interface State {
formConf: any;
formData: any;
config: any;
loading: boolean;
key: number;
dataForm: any;
formOperates: any[];
title: string;
relationData: any;
detailVisible: boolean;
}
defineOptions({ name: 'Detail' });
const emit = defineEmits(['close']);
const { t } = useI18n();
const [registerPopup, { openPopup, closePopup, setPopupProps }] = usePopup();
const [registerModal, { openModal, closeModal, setModalProps }] = useModal();
const [registerDrawer, { openDrawer, closeDrawer, setDrawerProps }] = useDrawer();
const parserRef = ref<any>(null);
const detailRef = ref<any>(null);
const state = reactive<State>({
formConf: {},
formData: {},
config: {},
loading: false,
key: +new Date(),
dataForm: {
id: '',
data: '',
},
formOperates: [],
title: t('common.detailText'),
relationData: {},
detailVisible: false,
});
const { title, formConf, formData, relationData, key, loading, detailVisible } = toRefs(state);
defineExpose({ init });
function fillFormData(form, data) {
let relationFormAttrList: any[] = [];
const loop = (list, parent?) => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.__vModel__) {
if (item.__config__.yunzhupaasKey === 'relationForm' || item.__config__.yunzhupaasKey === 'popupSelect') {
item.__config__.defaultValue = data[item.__vModel__ + '_id'];
item.name = data[item.__vModel__] || '';
} else {
const val = data.hasOwnProperty(item.__vModel__) ? data[item.__vModel__] : item.__config__.defaultValue;
item.__config__.defaultValue = val;
}
if (state.config.useFormPermission) {
let id = item.__config__.isSubTable ? parent.__vModel__ + '-' + item.__vModel__ : item.__vModel__;
let noShow = true;
if (state.formOperates && state.formOperates.length) {
noShow = !state.formOperates.some(o => o.enCode === id);
}
noShow = item.__config__.noShow ? item.__config__.noShow : noShow;
item.__config__.noShow = noShow;
}
}
if (['relationFormAttr', 'popupAttr'].includes(item.__config__.yunzhupaasKey)) relationFormAttrList.push(item);
if (item.__config__ && item.__config__.children && Array.isArray(item.__config__.children)) {
loop(item.__config__.children, item);
}
}
};
loop(form.fields);
}
function init(data) {
state.config = data;
state.formConf = cloneDeep(data.formConf);
state.dataForm.id = data.id;
openForm();
nextTick(() => {
setTimeout(initData, 0);
});
}
function initData() {
changeLoading(true);
state.loading = true;
if (state.config.id) {
getInfo(state.config.id);
} else {
closeForm();
}
}
function getInfo(id) {
getDataChange(state.config.modelId, id, state.config.encryption).then(res => {
state.dataForm = res.data || {};
if (!state.dataForm.data) return;
state.formData = JSON.parse(state.dataForm.data);
fillFormData(state.formConf, state.formData);
initRelationForm(state.formConf.fields);
nextTick(() => {
state.loading = false;
state.key = +new Date();
changeLoading(false);
});
});
}
function initRelationForm(componentList) {
componentList.forEach(cur => {
const config = cur.__config__;
if (config.yunzhupaasKey == 'relationFormAttr' || config.yunzhupaasKey == 'popupAttr') {
const relationKey = cur.relationField.split('_yunzhupaasTable_')[0];
componentList.forEach(item => {
const noVisibility = Array.isArray(item.__config__.visibility) && !item.__config__.visibility.includes('pc');
if (relationKey == item.__vModel__ && (noVisibility || !!item.__config__.noShow) && !cur.__vModel__) {
cur.__config__.noShow = true;
}
});
}
if (cur.__config__.children && cur.__config__.children.length) initRelationForm(cur.__config__.children);
});
}
function openForm() {
if (state.formConf.popupType === 'fullScreen') return openPopup();
if (state.formConf.popupType === 'drawer') return openDrawer();
openModal();
}
function closeForm() {
if (state.formConf.popupType === 'fullScreen') return closePopup();
if (state.formConf.popupType === 'drawer') return closeDrawer();
closeModal();
}
function setFormProps(data) {
if (state.formConf.popupType === 'fullScreen') return setPopupProps(data);
if (state.formConf.popupType === 'drawer') return setDrawerProps(data);
setModalProps(data);
}
function changeLoading(loading) {
setFormProps({ loading });
}
async function onClose() {
emit('close');
return true;
}
</script>

View File

@@ -0,0 +1,675 @@
<template>
<div class="yunzhupaas-content-wrapper short-link-wrapper short-link-wrapper-list">
<div class="short-link-lock-wrapper" v-show="columnPassUse">
<div class="short-link-lock-form">
<a-input-group compact class="enter-y">
<a-input-password v-model:value="password" :placeholder="t('views.dynamicModel.passwordPlaceholder')" @keyup.enter="unLock()" />
<a-button :loading="btnLoading" @click="unLock()">
<template #icon><unlock-outlined /></template>
</a-button>
</a-input-group>
</div>
</div>
<div class="short-link-main" v-show="!columnPassUse">
<a-popover placement="bottomRight">
<template #content>
<p class="shortLink-tip">{{ t('views.dynamicModel.scanAndShare') }}</p>
<QrCode :value="state.columnLink" :width="154" :options="{ margin: 1 }" class="my-5px" />
</template>
<i class="ym-custom ym-custom-qrcode icon-qrcode"></i>
</a-popover>
<div class="short-link-header">
{{ config.fullName }}
</div>
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box" v-if="columnData.searchList?.length">
<BasicForm
@register="registerSearchForm"
:schemas="searchSchemas"
@advanced-change="redoHeight"
@submit="handleSearchSubmit"
@reset="handleSearchReset"
class="search-form">
</BasicForm>
</div>
<div class="yunzhupaas-content-wrapper-content">
<BasicTable @register="registerTable" v-bind="getTableBindValue" ref="tableRef" @columns-change="handleColumnChange">
<template #expandedRowRender="{ record }" v-if="getChildTableStyle === 2 && childColumnList.length">
<a-tabs size="small">
<a-tab-pane :key="cIndex" :tab="child.label" :label="child.label" v-for="(child, cIndex) in childColumnList">
<a-table size="small" :data-source="record[child.prop]" :columns="child.children" :pagination="false" :scroll="{ x: 'max-content' }">
<template #bodyCell="{ column, record }">
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.dataIndex]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.dataIndex]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.dataIndex]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="columnData.showOverflow"
detailed />
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</template>
<template #bodyCell="{ column, record }">
<template v-for="(item, index) in childColumnList" v-if="getChildTableStyle !== 2 && childColumnList.length">
<template v-if="column.id?.includes('-') && item.children && item.children[0] && column.key === item.children[0]?.dataIndex">
<ChildTableColumn
:data="record[item.prop]"
:head="item.children"
@toggleExpand="toggleExpand(record, `${item.prop}Expand`)"
:expand="record[`${item.prop}Expand`]"
:showOverflow="columnData.showOverflow"
:key="index" />
</template>
</template>
<template v-if="!(record.top || column.id?.includes('-'))">
<template v-if="column.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="record[column.dataIndex]" :precision="column.precision" :thousands="column.thousands" disabled detailed />
</template>
<template v-if="column.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="record[column.dataIndex]"
:isStorage="column.isStorage"
:precision="column.precision"
:thousands="column.thousands"
:roundType="column.roundType"
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.dataIndex]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.dataIndex]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.dataIndex]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="columnData.showOverflow"
detailed />
</template>
</template>
<template v-if="column.key === 'action' && (!record.top || columnData.type == 5)">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
<template #summary v-if="columnData.showSummary && [1, 2, 4].includes(columnData.type)">
<a-table-summary fixed>
<a-table-summary-row>
<a-table-summary-cell :index="0">{{ t('component.table.summary') }}</a-table-summary-cell>
<a-table-summary-cell v-for="(item, index) in getColumnSum" :key="index" :index="index + 1">{{ item }}</a-table-summary-cell>
<a-table-summary-cell :index="getColumnSum.length + 1"></a-table-summary-cell>
</a-table-summary-row>
</a-table-summary>
</template>
</BasicTable>
</div>
</div>
</div>
<Detail ref="detailRef" />
</div>
</template>
<script lang="ts" setup>
import { getModelList, getConfig, checkPwd } from '@/api/onlineDev/shortLink';
import { ref, reactive, onMounted, toRefs, computed, unref, nextTick } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicForm, useForm } from '@/components/Form';
import { BasicTable, useTable, TableAction, ActionItem, TableActionType, SorterResult } from '@/components/Table';
import Detail from './detail/index.vue';
import ChildTableColumn from '../../dynamicModel/list/ChildTableColumn.vue';
import { getScriptFunc, thousandsFormat } from '@/utils/yunzhupaas';
import { getSearchFormSchemas } from '@/components/FormGenerator/src/helper/transform';
import { cloneDeep } from 'lodash-es';
import { QrCode } from '@/components/Qrcode/index';
import { UnlockOutlined } from '@ant-design/icons-vue';
import { encryptByMd5 } from '@/utils/cipher';
interface State {
config: any;
columnData: any;
formConf: any;
hasBatchBtn: boolean;
columnBtnsList: any[];
customBtnsList: any[];
columnOptions: any[];
treeFieldNames: any;
leftTreeData: any[];
leftTreeLoading: boolean;
treeActiveId: string;
treeActiveNodePath: any;
columns: any[];
complexColumns: any[];
childColumnList: any[];
exportList: any[];
cacheList: any[];
currFlow: any;
isCustomCopy: boolean;
candidateType: number;
currRow: any;
workFlowFormData: any;
expandObj: any;
columnSettingList: any[];
searchSchemas: any[];
treeRelationObj: any;
customRow: any;
customCell: any;
columnLink: string;
btnLoading: boolean;
key: number;
shortLinkId: string;
columnPassUse: number;
password: string;
realSearchList: any[];
realColumnList: any[];
}
const props = defineProps(['config', 'modelId', 'isPreview']);
const { createMessage } = useMessage();
const { t } = useI18n();
const tableRef = ref<Nullable<TableActionType>>(null);
const detailRef = ref<any>(null);
const searchInfo = reactive({
modelId: '',
queryJson: '',
superQueryJson: '',
encryption: props.config.encryption,
});
const state = reactive<State>({
config: {},
columnData: {},
formConf: {},
hasBatchBtn: false,
columnBtnsList: [],
customBtnsList: [],
columnOptions: [],
treeFieldNames: {
children: 'children',
title: 'fullName',
key: 'id',
isLeaf: 'isLeaf',
},
leftTreeData: [],
leftTreeLoading: false,
treeActiveId: '',
treeActiveNodePath: [],
columns: [],
complexColumns: [], // 复杂表头
childColumnList: [],
exportList: [],
cacheList: [],
currFlow: {},
isCustomCopy: false,
candidateType: 1,
currRow: {},
workFlowFormData: {},
expandObj: {},
columnSettingList: [],
searchSchemas: [],
treeRelationObj: null,
customRow: null,
customCell: null,
columnLink: '',
btnLoading: false,
key: +new Date(),
shortLinkId: '',
columnPassUse: -1,
password: '',
realSearchList: [],
realColumnList: [],
});
const { columnData, childColumnList, searchSchemas, config, btnLoading, columnPassUse, password } = toRefs(state);
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',
'iframe',
'steps',
'stepItem',
];
const selectFieldsList = ['radio', 'checkbox', 'select', 'treeSelect', 'cascader'];
const [registerSearchForm, { submit: searchFormSubmit }] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
});
const [registerTable, { reload, setLoading, redoHeight }] = useTable({
api: getModelList,
immediate: false,
clickToRowSelect: false,
resizeHeightOffset: -10,
// scroll: { x: 'max-content' },
afterFetch: data => {
// 行内编辑
if (state.columnData.type === 4) {
const list = data.map(o => ({ ...o, rowEdit: false }));
state.cacheList = cloneDeep(list);
return list;
}
let list = data.map(o => ({
...o,
...state.expandObj,
}));
state.cacheList = cloneDeep(list);
// 分组表格
if (state.columnData.type === 3) {
list.map(o => {
if (o.children && o.children.length) {
o.children = o.children.map(e => ({
...e,
...state.expandObj,
}));
}
});
}
return list;
},
});
const getPagination = computed(() => {
if ([3, 5].includes(state.columnData.type) || !state.columnData.hasPage) return false;
return { pageSize: state.columnData.pageSize };
});
const getChildTableStyle = computed(() => (state.columnData.type == 3 || state.columnData.type == 5 ? 1 : state.columnData.childTableStyle));
const getColumns = computed(() => (unref(getChildTableStyle) == 2 || state.columnData.type == 4 ? state.columns : state.complexColumns));
const getTableBindValue = computed(() => {
let columns = unref(getColumns);
const defaultSortConfig = (state.columnData.defaultSortConfig || []).map(o => (o.sort === 'desc' ? '-' : '') + o.field);
const data: any = {
pagination: unref(getPagination),
searchInfo: unref(searchInfo),
defSort: { sidx: defaultSortConfig.join(',') },
sortFn: (sortInfo: SorterResult | SorterResult[]) => {
if (Array.isArray(sortInfo)) {
const sortList = sortInfo.map(o => (o.order === 'descend' ? '-' : '') + o.field);
return { sidx: sortList.join(',') };
} else {
const { field, order } = sortInfo;
if (field && order) {
// 排序字段
return { sidx: (order === 'descend' ? '-' : '') + field };
} else {
return {};
}
}
},
columns,
clearSelectOnPageChange: true,
ellipsis: !!state.columnData.showOverflow,
isTreeTable: [3, 5].includes(state.columnData.type),
bordered: (unref(getChildTableStyle) != 2 && !!state.childColumnList?.length) || !!state.columnData.complexHeaderList?.length,
};
data.actionColumn = {
width: 50,
title: t('component.table.action'),
dataIndex: 'action',
fixed: 'right',
};
if (state.customRow) data.customRow = state.customRow;
return data;
});
const getSummaryColumn = computed(() => {
let defaultColumns = unref(getColumns);
// 处理列固定
if (state.columnSettingList?.length) {
for (let i = 0; i < defaultColumns.length; i++) {
inner: for (let j = 0; j < state.columnSettingList.length; j++) {
if (defaultColumns[i].dataIndex === state.columnSettingList[j].dataIndex) {
defaultColumns[i].fixed = state.columnSettingList[j].fixed;
defaultColumns[i].visible = state.columnSettingList[j].visible;
break inner;
}
}
}
defaultColumns = defaultColumns.filter(o => o.visible);
}
let columns: any[] = [];
for (let i = 0; i < defaultColumns.length; i++) {
const e = defaultColumns[i];
if (e.yunzhupaasKey === 'table' || e.yunzhupaasKey === 'complexHeader') {
if (e.children?.length) columns.push(...e.children);
} else {
columns.push(e);
}
if (e.fixed && e.children?.length) {
for (let j = 0; j < e.children.length; j++) {
e.children[j].fixed = e.fixed;
}
}
}
const leftFixedList = columns.filter(o => o.fixed === 'left');
const rightFixedList = columns.filter(o => o.fixed === 'right');
const noFixedList = columns.filter(o => o.fixed !== 'left' && o.fixed !== 'right');
return [...leftFixedList, ...rightFixedList, ...noFixedList];
});
// 列表合计
const getColumnSum = computed(() => {
const sums: any[] = [];
const isSummary = key => state.columnData.summaryField.includes(key);
const useThousands = key => unref(getSummaryColumn).some(o => o.__vModel__ === key && o.thousands);
unref(getSummaryColumn).forEach((column, index) => {
let sumVal = state.cacheList.reduce((sum, d) => sum + getCmpValOfRow(d, column.prop), 0);
if (!isSummary(column.prop)) sumVal = '';
sumVal = Number.isNaN(sumVal) ? '' : sumVal;
const realVal = sumVal && !Number.isInteger(sumVal) ? Number(sumVal).toFixed(2) : sumVal;
sums[index] = useThousands(column.prop) ? thousandsFormat(realVal) : realVal;
});
if ([1, 2].includes(state.columnData.type) && unref(getChildTableStyle) === 2 && state.childColumnList.length) sums.unshift('');
return sums;
});
function getCmpValOfRow(row, key) {
const isSummary = key => state.columnData.summaryField.includes(key);
if (!state.columnData.summaryField.length || !isSummary(key)) return 0;
const target = row[key];
if (!target) return 0;
const data = isNaN(target) ? 0 : Number(target);
return data;
}
function getTableActions(record): ActionItem[] {
return [{ label: t('common.detailText'), onClick: columnBtnsHandle.bind(null, 'detail', record) }];
}
// 行按钮点击事件
function columnBtnsHandle(key, record) {
if (key === 'detail') return goDetail(record);
}
// 查看详情
function goDetail(record) {
const formConf = cloneDeep(state.formConf);
formConf.fields = getRealFields(formConf.fields);
const data = {
id: record.id,
formConf: formConf,
modelId: props.modelId,
useFormPermission: false,
encryption: props.config.encryption,
};
detailRef.value?.init(data);
}
function validFields(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;
}
function getRealFields(list) {
let newList = list.filter(item => validFields(item));
newList.forEach(o => o.__config__?.children && Array.isArray(o.__config__.children) && (o.__config__.children = getRealFields(o.__config__.children)));
return newList;
}
function init() {
state.config = {
modelId: props.modelId,
isPreview: props.isPreview,
...props.config,
};
searchInfo.modelId = props.modelId;
if (!state.config.columnData || (state.config.webType != '4' && !state.config.formData)) return;
state.columnData = JSON.parse(state.config.columnData);
state.columnData.type = 1;
state.columnData.searchList = state.realSearchList;
state.columnData.columnList = state.realColumnList;
if (state.columnData.type === 3) {
state.columnData.columnList = state.columnData.columnList.filter(o => o.prop != state.columnData.groupField);
}
state.hasBatchBtn = state.columnData.btnsList.some(o => ['batchRemove', 'batchPrint'].includes(o.value));
state.formConf = state.config.formData ? JSON.parse(state.config.formData) : {};
state.formConf.popupType = 'general';
state.columnOptions = state.columnData.columnOptions || [];
setLoading(true);
if (state.columnData.funcs.rowStyle) {
state.customRow = (record, index) => {
const data = { row: record, rowIndex: index };
const func: any = getScriptFunc(state.columnData.funcs.rowStyle);
const style: any = func ? func(data) : null;
if (!style) return {};
return { style };
};
}
if (state.columnData.funcs.cellStyle) {
state.customCell = (record, rowIndex, column) => {
const data = { row: record, rowIndex, column, columnIndex: column.key };
const func: any = getScriptFunc(state.columnData.funcs.cellStyle);
const style: any = func ? func(data) : null;
if (!style) return {};
return { style };
};
}
getSearchSchemas();
getColumnList();
if (props.isPreview) return setLoading(false);
nextTick(() => {
state.columnData.searchList?.length ? searchFormSubmit() : reload({ page: 1 });
});
}
function getSearchSchemas() {
if (state.columnData.treeRelation) {
for (let i = 0; i < state.columnData.searchList.length; i++) {
const e = state.columnData.searchList[i];
if (e.id === state.columnData.treeRelation) {
state.treeRelationObj = e;
break;
}
}
// 搜索字段里无左侧树关联字段时,去全部字段里获取关联字段属性
if (!state.treeRelationObj) {
for (let i = 0; i < state.columnData.columnOptions.length; i++) {
const e = state.columnData.columnOptions[i];
if (e.id === state.columnData.treeRelation) {
state.treeRelationObj = { ...e, searchMultiple: false, yunzhupaasKey: e.__config__.yunzhupaasKey };
break;
}
}
}
}
const schemas = getSearchFormSchemas(state.columnData.searchList);
state.searchSchemas = schemas;
}
function getColumnList() {
let columnList: any[] = [];
columnList = state.columnData.columnList;
state.exportList = columnList;
let columns = columnList.map(o => ({
...o,
title: o.label,
dataIndex: o.prop,
align: o.align,
fixed: o.fixed == 'none' ? false : o.fixed,
sorter: o.sortable,
width: o.width || 100,
customCell: state.customCell || null,
}));
if (state.columnData.type !== 3 && state.columnData.type !== 5) columns = getComplexColumns(columns);
state.columns = columns.filter(o => o.prop.indexOf('-') < 0);
getChildComplexColumns(columns);
}
function getComplexColumns(columns) {
let complexHeaderList: any[] = state.columnData.complexHeaderList || [];
if (!complexHeaderList.length) return columns;
let childColumns: any[] = [];
let firstChildColumns: string[] = [];
for (let i = 0; i < complexHeaderList.length; i++) {
const e = complexHeaderList[i];
e.title = e.fullNameI18nCode ? t(e.fullNameI18nCode, e.fullName) : e.fullName;
e.align = e.align;
e.dataIndex = e.id;
e.prop = e.id;
e.children = [];
e.yunzhupaasKey = 'complexHeader';
if (e.childColumns?.length) {
childColumns.push(...e.childColumns);
for (let k = 0; k < e.childColumns.length; k++) {
const item = e.childColumns[k];
for (let j = 0; j < columns.length; j++) {
const o = columns[j];
if (o.prop == item && o.fixed !== 'left' && o.fixed !== 'right') e.children.push({ ...o });
}
}
}
if (e.children.length) firstChildColumns.push(e.children[0].prop);
}
complexHeaderList = complexHeaderList.filter(o => o.children.length);
let list: any[] = [];
for (let i = 0; i < columns.length; i++) {
const e = columns[i];
if (!childColumns.includes(e.prop) || e.fixed === 'left' || e.fixed === 'right') {
list.push(e);
} else {
if (firstChildColumns.includes(e.prop)) {
const item = complexHeaderList.find(o => o.childColumns.includes(e.prop));
list.push(item);
}
}
}
return list;
}
function getChildComplexColumns(columnList) {
let list: any[] = [];
for (let i = 0; i < columnList.length; i++) {
const e = columnList[i];
if (!e.prop.includes('-')) {
list.push(e);
} else {
let prop = e.prop.split('-')[0];
let vModel = e.prop.split('-')[1];
let label = e.label.split('-')[0];
let childLabel = e.label.replace(label + '-', '');
if (e.fullNameI18nCode && Array.isArray(e.fullNameI18nCode) && e.fullNameI18nCode[0]) label = t(e.fullNameI18nCode[0], label);
let newItem = {
align: 'center',
yunzhupaasKey: 'table',
prop,
label,
title: label,
dataIndex: prop,
children: [],
customCell: state.customCell || null,
};
e.dataIndex = vModel;
e.title = e.__config__?.labelI18nCode ? t(e.__config__.labelI18nCode, childLabel) : childLabel;
if (!state.expandObj.hasOwnProperty(`${prop}Expand`)) state.expandObj[`${prop}Expand`] = false;
if (!list.some(o => o.prop === prop)) list.push(newItem);
for (let i = 0; i < list.length; i++) {
if (list[i].prop === prop) {
list[i].children.push(e);
break;
}
}
}
}
if (unref(getChildTableStyle) != 2) getMergeList(list);
state.complexColumns = list;
state.childColumnList = list.filter(o => o.yunzhupaasKey === 'table');
}
function getMergeList(list) {
list.forEach(item => {
if (item.yunzhupaasKey === 'table' && item.children && item.children.length) {
item.children.forEach((child, index) => {
if (index == 0) {
child.customCell = (record, rowIndex, column) => ({
...(state.customCell ? state.customCell(record, rowIndex, column) : {}),
...{
rowspan: 1,
colspan: item.children.length,
class: 'child-table-box',
},
});
} else {
child.customCell = () => ({
rowspan: 0,
colspan: 0,
});
}
});
}
});
}
function toggleExpand(row, field) {
row[field] = !row[field];
}
function handleColumnChange(data) {
state.columnSettingList = data;
}
function handleSearchSubmit(data) {
let obj = {};
for (let [key, value] of Object.entries(data)) {
if (value) {
if (Array.isArray(value)) {
if (value.length) obj[key] = value;
} else {
obj[key] = value;
}
}
}
searchInfo.queryJson = JSON.stringify(obj) === '{}' ? '' : JSON.stringify(obj);
reload({ page: 1 });
}
function handleSearchReset() {
searchFormSubmit();
}
function unLock() {
if (!state.password) return createMessage.error(t('views.dynamicModel.passwordPlaceholder'));
state.btnLoading = true;
const query = {
id: state.shortLinkId,
type: 1,
encryption: props.config.encryption,
password: encryptByMd5(state.password),
};
checkPwd(query)
.then(() => {
state.btnLoading = false;
state.columnPassUse = 0;
init();
})
.catch(() => {
state.btnLoading = false;
});
}
onMounted(() => {
getConfig(props.modelId, props.config.encryption).then(res => {
state.columnLink = res.data.columnLink || '';
state.shortLinkId = res.data.id || '';
state.columnPassUse = res.data.columnPassUse || 0;
state.realSearchList = res.data.columnCondition ? JSON.parse(res.data.columnCondition) : [];
state.realColumnList = res.data.columnText ? JSON.parse(res.data.columnText) : [];
if (state.columnPassUse) return;
init();
});
});
</script>

View File

@@ -0,0 +1,225 @@
<template>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" :title="title" width="800px" showFooter :showOkBtn="false">
<template #insertFooter> </template>
<a-row class="p-10px dynamic-form">
<!-- 表单 -->
<a-form :colon="false" size="middle" layout="horizontal" labelAlign="right" :labelCol="{ style: { width: '100px' } }" :model="dataForm" ref="formRef">
<a-row :gutter="15">
<!-- 具体表单 -->
<a-col :span="24" class="ant-col-item" v-if="hasFormP('lead_name')">
<a-form-item name="lead_name">
<template #label>线索名称 </template>
<YunzhupaasInput
v-model:value="dataForm.lead_name"
placeholder="请输入线索名称"
:maxlength="50"
disabled
detailed
allowClear
:style="{ width: '100%' }"
:maskConfig="maskConfig.lead_name">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('mobile')">
<a-form-item name="mobile">
<template #label>手机号 </template>
<YunzhupaasInput
v-model:value="dataForm.mobile"
placeholder="请输入手机号"
:maxlength="11"
disabled
detailed
allowClear
:style="{ width: '100%' }"
:maskConfig="maskConfig.mobile">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('email')">
<a-form-item name="email">
<template #label>邮箱 </template>
<YunzhupaasInput
v-model:value="dataForm.email"
placeholder="请输入邮箱"
:maxlength="50"
disabled
detailed
allowClear
:style="{ width: '100%' }"
:maskConfig="maskConfig.email">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('lead_status')">
<a-form-item name="lead_status">
<template #label>状态 </template> <p>{{ dataForm.lead_status }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('sales_id')">
<a-form-item name="sales_id">
<template #label>销售人员 </template> <p>{{ dataForm.sales_id }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('customer_source')">
<a-form-item name="customer_source">
<template #label>来源 </template> <p>{{ dataForm.customer_source }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('remark')">
<a-form-item name="remark">
<template #label>备注 </template> <p>{{ dataForm.remark }}</p>
</a-form-item>
</a-col>
<!-- 表单结束 -->
</a-row>
</a-form>
</a-row>
</BasicDrawer>
<!-- 有关联表单详情开始 -->
<RelationDetail ref="relationDetailRef" />
<!-- 有关联表单详情结束 -->
</template>
<script lang="ts" setup>
import { getDetailInfo } from './helper/api';
import { getConfigData } from '@/api/onlineDev/visualDev';
import { reactive, toRefs, nextTick, ref, computed, unref, toRaw } from 'vue';
import { BasicModal, useModal } from '@/components/Modal';
import { BasicDrawer, useDrawer } from '@/components/Drawer';
// 有关联表单详情
import RelationDetail from '@/views/common/dynamicModel/list/detail/index.vue';
// 表单权限
import { usePermission } from '@/hooks/web/usePermission';
import { useMessage } from '@/hooks/web/useMessage';
import { CaretRightOutlined } from '@ant-design/icons-vue';
import { buildUUID } from '@/utils/uuid';
import { useI18n } from '@/hooks/web/useI18n';
import { getDataChange } from '@/api/onlineDev/visualDev';
import { getDataInterfaceDataInfoByIds } from '@/api/systemData/dataInterface';
import ExtraRelationInfo from '@/components/yunzhupaas/RelationForm/src/ExtraRelationInfo.vue';
interface State {
dataForm: any;
title: string;
maskConfig: any;
interfaceRes: any;
locationScope: any;
extraOptions: any;
extraData: any;
}
defineOptions({ name: 'Detail' });
const { createMessage, createConfirm } = useMessage();
const [registerDrawer, { openDrawer, setDrawerProps, closeDrawer }] = useDrawer();
const { t } = useI18n();
const relationDetailRef = ref<any>(null);
const state = reactive<State>({
dataForm: {},
title: t('common.detailText', '详情'),
maskConfig: {
lead_name: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
mobile: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
email: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
},
interfaceRes: { lead_status: [], mobile: [], lead_name: [], customer_source: [], remark: [], sales_id: [], email: [] },
locationScope: {},
extraOptions: {},
extraData: {},
});
const { title, dataForm, maskConfig } = toRefs(state);
// 表单权限
const { hasFormP } = usePermission();
defineExpose({ init });
function init(data) {
state.dataForm.id = data.id;
openDrawer();
nextTick(() => {
setTimeout(initData, 0);
});
}
function initData() {
changeLoading(true);
if (state.dataForm.id) {
getData(state.dataForm.id);
} else {
closeDrawer();
}
}
function getData(id) {
getDetailInfo(id).then(res => {
state.dataForm = res.data || {};
nextTick(() => {
changeLoading(false);
});
});
}
function toDetail(modelId, id, propsValue) {
if (!id) return;
getConfigData(modelId).then(res => {
if (!res.data || !res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = 'general';
formConf.hasPrintBtn = false;
formConf.customBtns = [];
const data = { id, formConf, modelId, propsValue };
relationDetailRef.value?.init(data);
});
}
function setFormProps(data) {
setDrawerProps(data);
}
function changeLoading(loading) {
setFormProps({ loading });
}
function getParamList(key) {
let templateJson: any[] = state.interfaceRes[key];
if (!templateJson || !templateJson.length || !state.dataForm) return templateJson;
for (let i = 0; i < templateJson.length; i++) {
if (templateJson[i].relationField && templateJson[i].sourceType == 1) {
templateJson[i].defaultValue = state.dataForm[templateJson[i].relationField + '_id'] || '';
}
}
return templateJson;
}
</script>

View File

@@ -0,0 +1,457 @@
<template>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" width="800px" showFooter
:cancelText="t('common.cancelText','取消')"
:okText="t('common.okText','确定')"
@ok="handleSubmit" :closeFunc="onClose">
<template #title>
<a-space :size="10">
<div class="text-16px font-medium">{{ title }}</div>
<a-space-compact size="small" block v-if="dataForm.id">
<a-tooltip :title="t('common.prevRecord')">
<a-button size="small" :disabled="getPrevDisabled" @click="handlePrev">
<i class="icon-ym icon-ym-caret-left text-10px"></i>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.nextRecord')">
<a-button size="small" :disabled="getNextDisabled" @click="handleNext">
<i class="icon-ym icon-ym-caret-right text-10px"></i>
</a-button>
</a-tooltip>
</a-space-compact>
</a-space>
</template>
<template #insertFooter>
<div class="float-left mt-5px">
<yunzhupaasCheckboxSingle v-model:value="submitType" :label="continueText" />
</div>
</template>
<a-row class="p-10px dynamic-form ">
<!-- 表单 -->
<a-form :colon="false" size="middle" layout= "horizontal"
labelAlign= "right"
:labelCol="{ style: { width: '100px' } }" :model="dataForm" :rules="dataRule" ref="formRef" >
<a-row :gutter="15">
<!-- 具体表单 -->
<a-col :span="24" class="ant-col-item" v-if="hasFormP('lead_name')"
>
<a-form-item
name="lead_name" >
<template #label>线索名称
</template> <YunzhupaasInput v-model:value="dataForm.lead_name" @change="changeData('lead_name',-1)"
placeholder="请输入线索名称" :maxlength="50" :allowClear='true' :style='{"width":"100%"}' :maskConfig = "maskConfig.lead_name" :showCount = "false" >
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('mobile')"
>
<a-form-item
name="mobile" >
<template #label>手机号
</template> <YunzhupaasInput v-model:value="dataForm.mobile" @change="changeData('mobile',-1)"
placeholder="请输入手机号" :maxlength="11" :allowClear='true' :style='{"width":"100%"}' :maskConfig = "maskConfig.mobile" :showCount = "false" >
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('email')"
>
<a-form-item
name="email" >
<template #label>邮箱
</template> <YunzhupaasInput v-model:value="dataForm.email" @change="changeData('email',-1)"
placeholder="请输入邮箱" :maxlength="50" :allowClear='true' :style='{"width":"100%"}' :maskConfig = "maskConfig.email" :showCount = "false" >
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('lead_status')"
>
<a-form-item
name="lead_status" >
<template #label>状态
</template> <YunzhupaasSelect v-model:value="dataForm.lead_status" @change="changeData('lead_status',-1)"
placeholder="请选择状态" :templateJson="state.interfaceRes.lead_status" :allowClear='true' :style='{"width":"100%"}' :showSearch='false' :options="optionsObj.lead_statusOptions" :fieldNames="optionsObj.lead_statusProps"
>
</YunzhupaasSelect>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('sales_id')"
>
<a-form-item
name="sales_id" >
<template #label>销售人员
</template> <YunzhupaasUsersSelect v-model:value="dataForm.sales_id" @change="changeData('sales_id',-1)"
placeholder="请选择销售人员" :allowClear='true' :style='{"width":"100%"}' selectType="all" >
</YunzhupaasUsersSelect>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('customer_source')"
>
<a-form-item
name="customer_source" >
<template #label>来源
</template> <YunzhupaasSelect v-model:value="dataForm.customer_source" @change="changeData('customer_source',-1)"
placeholder="请选择来源" :templateJson="state.interfaceRes.customer_source" :allowClear='true' :style='{"width":"100%"}' :showSearch='false' :options="optionsObj.customer_sourceOptions" :fieldNames="optionsObj.customer_sourceProps"
>
</YunzhupaasSelect>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item" v-if="hasFormP('remark')"
>
<a-form-item
name="remark" >
<template #label>备注
</template> <YunzhupaasTextarea v-model:value="dataForm.remark" @change="changeData('remark',-1)"
placeholder="请输入备注" :maxlength="200" :allowClear='true' :style='{"width":"100%"}' :autoSize='{"minRows":4,"maxRows":4}' :showCount = "false" >
</YunzhupaasTextarea>
</a-form-item>
</a-col>
<!-- 表单结束 -->
</a-row>
</a-form>
</a-row>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { create, update, getInfo } from './helper/api';
import { reactive, toRefs, nextTick, ref, unref, computed,toRaw, inject } from 'vue';
import { BasicDrawer, useDrawer } from '@/components/Drawer';
import { yunzhupaasRelationForm } from '@/components/yunzhupaas';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import type { FormInstance } from 'ant-design-vue';
import { thousandsFormat , getDateTimeUnit, getTimeUnit} from '@/utils/yunzhupaas';
import { getDictionaryDataSelector } from '@/api/systemData/dictionary';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import dayjs from 'dayjs';
// 表单权限
import { usePermission } from '@/hooks/web/usePermission';
import { cloneDeep } from 'lodash-es';
import { buildUUID } from '@/utils/uuid';
import { CaretRightOutlined } from '@ant-design/icons-vue';
interface State {
dataForm: any;
tableRows: any;
dataRule: any;
optionsObj: any;
childIndex: any;
isEdit: any;
interfaceRes: any;
//可选范围默认值
ableAll: any;
//掩码配置
maskConfig:any;
//定位属性
locationScope:any;
extraOptions: any;
title: string;
continueText: string; allList: any[];
currIndex: number;
isContinue: boolean;
submitType: number;
showContinueBtn: boolean;
}
const emit = defineEmits(['reload']);
const getLeftTreeActiveInfo: (() => any) | null = inject('getLeftTreeActiveInfo', null);
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const [registerDrawer, { openDrawer, setDrawerProps }] = useDrawer();
const formRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
lead_name:undefined,
mobile:undefined,
email:undefined,
lead_status:'Unassigned',
sales_id:undefined,
customer_source:'',
remark:undefined,
version: 0,
},
tableRows:{
},
dataRule: {
lead_name: [
{
required: true,
message: t('sys.validate.textRequiredSuffix','不能为空'),
trigger: 'blur'
},
],
mobile: [
{
required: true,
message: t('sys.validate.textRequiredSuffix','不能为空'),
trigger: 'blur'
},
{
pattern: /^1[3456789]\d{9}$/,
message: t('sys.validate.mobilePhone','请输入正确的手机号码'),
trigger: 'blur'
},
],
email: [
{
pattern: /^[a-z0-9]+([._\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/,
message: t('sys.validate.email','请输入正确的邮箱'),
trigger: 'blur'
},
],
lead_status: [
{
required: true,
message: t('sys.validate.arrayRequiredPrefix ','请至少选择一个'),
trigger: 'change'
},
],
sales_id: [
{
required: true,
message: t('sys.validate.arrayRequiredPrefix ','请至少选择一个'),
trigger: 'change'
},
],
},
optionsObj:{
lead_statusOptions:[{"fullName":"待分配","id":"Unassigned"},{"fullName":"跟进中","id":"InProgress"},{"fullName":"已转换","id":"Converted"},{"fullName":"无效","id":"Invalid"}],
lead_statusProps:{"label":"fullName","value":"id" },
customer_sourceOptions:[],
customer_sourceProps:{"label":"fullName","value":"enCode" },
},
childIndex: -1,
isEdit: false,
interfaceRes: {"lead_status":[],"mobile":[],"lead_name":[],"customer_source":[],"remark":[],"sales_id":[],"email":[]},
//可选范围默认值
ableAll:{
},
//掩码配置
maskConfig:{
lead_name: {"prefixType":1,"useUnrealMask":false,"maskType":1,"unrealMaskLength":1,"prefixLimit":0,"suffixLimit":0,"filler":"*","prefixSpecifyChar":"","suffixType":1,"ignoreChar":"","suffixSpecifyChar":""} ,
mobile: {"prefixType":1,"useUnrealMask":false,"maskType":1,"unrealMaskLength":1,"prefixLimit":0,"suffixLimit":0,"filler":"*","prefixSpecifyChar":"","suffixType":1,"ignoreChar":"","suffixSpecifyChar":""} ,
email: {"prefixType":1,"useUnrealMask":false,"maskType":1,"unrealMaskLength":1,"prefixLimit":0,"suffixLimit":0,"filler":"*","prefixSpecifyChar":"","suffixType":1,"ignoreChar":"","suffixSpecifyChar":""} ,
},
//定位属性
locationScope:{
},
extraOptions:{
},
title: "",
continueText: "", allList: [],
currIndex: 0,
isContinue: false,
submitType: 0,
showContinueBtn: true ,
});
const { title, continueText, showContinueBtn, dataRule, dataForm, optionsObj, ableAll, maskConfig,submitType } = toRefs(state);
const getPrevDisabled = computed(() => state.currIndex === 0);
const getNextDisabled = computed(() => state.currIndex === state.allList.length - 1);
// 表单权限
const { hasFormP } = usePermission();
defineExpose({ init });
function init(data) {
state.submitType = 0;
state.isContinue = false;
state.title = !data.id ? t('common.add2Text','新增') : t('common.editText','编辑');
state.continueText = !data.id ? t('common.continueAndAddText','确定并新增') : t('common.continueText','确定并继续'); setFormProps({ continueLoading: false });
state.dataForm.id = data.id;
openDrawer();
state.allList = data.allList;
state.currIndex = state.allList.length && data.id ? state.allList.findIndex((item) => item.id === data.id) : 0;
nextTick(() => {
getForm().resetFields();
setTimeout(initData, 0);
});
}
function initData() {
changeLoading(true);
if (state.dataForm.id) {
getData(state.dataForm.id);
} else {
//初始化options
getcustomer_sourceOptions();
// 设置默认值
state.dataForm={
lead_name:undefined,
mobile:undefined,
email:undefined,
lead_status:'Unassigned',
sales_id:undefined,
customer_source:'',
remark:undefined,
version: 0,
};
if (getLeftTreeActiveInfo) state.dataForm = {...state.dataForm, ...(getLeftTreeActiveInfo() || {}) };
state.childIndex = -1;
changeLoading(false);
}
}
function getForm() {
const form = unref(formRef);
if (!form) {
throw new Error('form is null!');
}
return form;
}
function getData(id) {
getInfo(id).then((res) => {
state.dataForm = res.data || {};
getcustomer_sourceOptions();
state.childIndex = -1;
changeLoading(false);
});
}
async function handleSubmit(type) {
try {
const values = await getForm()?.validate();
if (!values) return;
setFormProps({ confirmLoading: true });
const formMethod = state.dataForm.id ? update : create;
formMethod(state.dataForm)
.then((res) => {
createMessage.success(res.msg);
setFormProps({ confirmLoading: false });
if (state.submitType == 1) {
initData();
state.isContinue = true;
} else {
setFormProps({ open: false });
emit('reload');
}
})
.catch(() => {
setFormProps({ confirmLoading: false });
});
} catch (_) {}
}
function handlePrev() {
state.currIndex--;
handleGetNewInfo();
}
function handleNext() {
state.currIndex++;
handleGetNewInfo();
}
function handleGetNewInfo() {
changeLoading(true);
getForm().resetFields();
const id = state.allList[state.currIndex].id;
getData(id);
}
function setFormProps(data) {
setDrawerProps(data);
}
function changeLoading(loading) {
setDrawerProps({ loading });
}
async function onClose() {
if (state.isContinue) emit('reload');
return true;
}
function changeData(model, index) {
state.isEdit = false
state.childIndex = index
for (let key in state.interfaceRes) {
if (key != model) {
let faceReList = state.interfaceRes[key]
for (let i = 0; i < faceReList.length; i++) {
let relationField = faceReList[i].relationField;
if(relationField){
let modelAll = relationField.split('-');
let faceMode = '';
let faceMode2 = modelAll.length == 2?modelAll[0].substring(0, modelAll[0].length-4) +modelAll[1]:""
for (let i = 0; i < modelAll.length; i++) {
faceMode += modelAll[i];
}
if (faceMode == model || faceMode2 == model ) {
let options = 'get' + key + 'Options';
eval(options)(true);
changeData(key, index)
}
}
}
}
}
}
function changeDataFormData(type, data, model,index,defaultValue) {
if(!state.isEdit) {
if (type == 2) {
for (let i = 0; i < state.dataForm[data].length; i++) {
if (index == -1) {
state.dataForm[data][i][model] = defaultValue
} else if (index == i) {
state.dataForm[data][i][model] = defaultValue
}
}
} else {
state.dataForm[data] = defaultValue
}
}
}
//数据选项--数据字典初始化方法
function getcustomer_sourceOptions() {
getDictionaryDataSelector('797256993944371205').then(res => {
state.optionsObj.customer_sourceOptions = res.data.list
})
}
function getRelationDate(timeRule, timeType, timeTarget, timeValueData, dataValue) {
let timeDataValue: any = null;
let timeValue = Number(timeValueData);
if (timeRule) {
if (timeType == 1) {
timeDataValue = timeValue;
} else if (timeType == 2) {
timeDataValue = dataValue;
} else if (timeType == 3) {
timeDataValue = new Date().getTime();
} else if (timeType == 4 || timeType == 5) {
const type = getTimeUnit(timeTarget);
const method = timeType == 4 ? 'subtract' : 'add';
timeDataValue = dayjs()[method](timeValue, type).valueOf();
}
}
return timeDataValue;
}
function getRelationTime(timeRule, timeType, timeTarget, timeValue, formatType, dataValue) {
let format = formatType == 'HH:mm' ? 'HH:mm:00' : formatType;
let timeDataValue: any = null;
if (timeRule) {
if (timeType == 1) {
timeDataValue = timeValue || '00:00:00';
if (timeDataValue.split(':').length == 3) {
timeDataValue = timeDataValue;
} else {
timeDataValue = timeDataValue + ':00';
}
} else if (timeType == 2) {
timeDataValue = dataValue;
} else if (timeType == 3) {
timeDataValue = dayjs().format(format);
} else if (timeType == 4 || timeType == 5) {
const type = getTimeUnit(timeTarget + 3);
const method = timeType == 4 ? 'subtract' : 'add';
timeDataValue = dayjs()[method](timeValue, type).format(format);
}
}
return timeDataValue;
}
</script>

View File

@@ -0,0 +1,34 @@
import { defHttp } from '@/utils/http/axios';
// 获取列表
export function getList(data) {
return defHttp.post({ url: '/api/bcm/CrmLead/getList', data });
}
// 新建
export function create(data) {
return defHttp.post({ url:'/api/bcm/CrmLead', data });
}
// 修改
export function update(data) {
return defHttp.put({ url: '/api/bcm/CrmLead/'+ data.id, data });
}
// 详情(无转换数据)
export function getInfo(id) {
return defHttp.get({ url: '/api/bcm/CrmLead/' + id });
}
// 获取(转换数据)
export function getDetailInfo(id) {
return defHttp.get({ url: '/api/bcm/CrmLead/detail/' + id });
}
// 删除
export function del(id) {
return defHttp.delete({ url: '/api/bcm/CrmLead/' + id });
}
// 批量删除数据
export function batchDelete(data) {
return defHttp.delete({ url: '/api/bcm/CrmLead/batchRemove', data });
}
// 导出
export function exportData(data) {
return defHttp.post({ url: '/api/bcm/CrmLead/Actions/Export', data });
}

View File

@@ -0,0 +1,503 @@
const columnList = [
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItemcd2c2e",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"线索名称",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574864579,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"prop":"lead_name",
"__vModel__":"lead_name",
"disabled":false,
"id":"lead_name",
"placeholder":"请输入线索名称",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"线索名称",
"label":"线索名称",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItem35dee5",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"手机号",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574898493,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"unique":true,
"tag":"YunzhupaasInput",
"regList":[
{
"pattern":"/^1[3456789]\\d{9}$/",
"message":"请输入正确的手机号码",
"messageI18nCode":"sys.validate.mobilePhone"
}
],
"tableAlign":"left",
"span":24
},
"readonly":false,
"prop":"mobile",
"__vModel__":"mobile",
"disabled":false,
"id":"mobile",
"placeholder":"请输入手机号",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":11,
"fullName":"手机号",
"label":"手机号",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItem4e5a2c",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"邮箱",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774574938265,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[
{
"pattern":"/^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/",
"message":"请输入正确的邮箱",
"messageI18nCode":"sys.validate.email"
}
],
"tableAlign":"left",
"span":24
},
"readonly":false,
"prop":"email",
"__vModel__":"email",
"disabled":false,
"id":"email",
"placeholder":"请输入邮箱",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"邮箱",
"label":"邮箱",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"状态",
"fullNameI18nCode":[
""
],
"label":"状态",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"id"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"Unassigned",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574965631,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem199e0c",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"static",
"dictionaryType":"",
"tipLabel":"",
"tableFixed":"none",
"label":"状态",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":24
},
"prop":"lead_status",
"width":null,
"options":[
{
"fullName":"待分配",
"id":"Unassigned"
},
{
"fullName":"跟进中",
"id":"InProgress"
},
{
"fullName":"已转换",
"id":"Converted"
},
{
"fullName":"无效",
"id":"Invalid"
}
],
"__vModel__":"lead_status",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"lead_status",
"placeholder":"请选择状态",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"usersSelect",
"clearable":true,
"resizable":true,
"ableIds":[],
"multiple":false,
"fullName":"销售人员",
"fullNameI18nCode":[
""
],
"label":"销售人员",
"sortable":false,
"align":"left",
"__config__":{
"formId":"formItem20008f",
"yunzhupaasKey":"usersSelect",
"visibility":[
"pc",
"app"
],
"defaultValue":null,
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"销售人员",
"trigger":"change",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774575084782,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-founder",
"defaultCurrent":false,
"tag":"YunzhupaasUsersSelect",
"regList":[],
"tableAlign":"left",
"span":24
},
"prop":"sales_id",
"width":null,
"__vModel__":"sales_id",
"fixed":"none",
"style":{
"width":"100%"
},
"selectType":"all",
"disabled":false,
"id":"sales_id",
"placeholder":"请选择销售人员",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"来源",
"fullNameI18nCode":[
""
],
"label":"来源",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774575125146,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem0800c0",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797256993944371205",
"tipLabel":"",
"tableFixed":"none",
"label":"来源",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":24
},
"prop":"customer_source",
"width":null,
"options":[],
"__vModel__":"customer_source",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"customer_source",
"placeholder":"请选择来源",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"textarea",
"clearable":true,
"resizable":true,
"maxlength":200,
"fullName":"备注",
"fullNameI18nCode":[
""
],
"label":"备注",
"sortable":false,
"align":"left",
"autoSize":{
"minRows":4,
"maxRows":4
},
"showCount":false,
"__config__":{
"formId":"formItem292367",
"yunzhupaasKey":"textarea",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"备注",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774575155402,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-textarea",
"tag":"YunzhupaasTextarea",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"prop":"remark",
"width":null,
"__vModel__":"remark",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"remark",
"placeholder":"请输入备注",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
}
]
export default columnList

View File

@@ -0,0 +1,146 @@
const searchList = [
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"showCount":false,
"__config__":{
"formId":"formItem35dee5",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"手机号",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574898493,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"unique":true,
"tag":"YunzhupaasInput",
"regList":[
{
"pattern":"/^1[3456789]\\d{9}$/",
"message":"请输入正确的手机号码",
"messageI18nCode":"sys.validate.mobilePhone"
}
],
"tableAlign":"left",
"span":24
},
"readonly":false,
"prop":"mobile",
"__vModel__":"mobile",
"searchMultiple":false,
"disabled":false,
"id":"mobile",
"placeholder":"请输入手机号",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"searchType":2,
"maxlength":11,
"fullName":"手机号",
"label":"手机号",
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"isKeyword":true,
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"searchType":1,
"multiple":false,
"fullName":"来源",
"fullNameI18nCode":[
""
],
"label":"来源",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774575125146,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem0800c0",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797256993944371205",
"tipLabel":"",
"tableFixed":"none",
"label":"来源",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":24
},
"prop":"customer_source",
"options":[],
"__vModel__":"customer_source",
"searchMultiple":true,
"isKeyword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"customer_source",
"placeholder":"请选择来源",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
}
]
export default searchList

View File

@@ -0,0 +1,440 @@
const superQueryJson = [
{
"clearable":true,
"maxlength":50,
"useScan":false,
"suffixIcon":"",
"fullName":"线索名称",
"fullNameI18nCode":[
""
],
"addonAfter":"",
"showCount":false,
"__config__":{
"formId":"formItemcd2c2e",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"线索名称",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574864579,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"__vModel__":"lead_name",
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"lead_name",
"placeholder":"请输入线索名称",
"prefixIcon":"",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":11,
"useScan":false,
"suffixIcon":"",
"fullName":"手机号",
"fullNameI18nCode":[
""
],
"addonAfter":"",
"showCount":false,
"__config__":{
"formId":"formItem35dee5",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"手机号",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574898493,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"unique":true,
"tag":"YunzhupaasInput",
"regList":[
{
"pattern":"/^1[3456789]\\d{9}$/",
"message":"请输入正确的手机号码",
"messageI18nCode":"sys.validate.mobilePhone"
}
],
"tableAlign":"left",
"span":24
},
"readonly":false,
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"__vModel__":"mobile",
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"mobile",
"placeholder":"请输入手机号",
"prefixIcon":"",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":50,
"useScan":false,
"suffixIcon":"",
"fullName":"邮箱",
"fullNameI18nCode":[
""
],
"addonAfter":"",
"showCount":false,
"__config__":{
"formId":"formItem4e5a2c",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"邮箱",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774574938265,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[
{
"pattern":"/^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/",
"message":"请输入正确的邮箱",
"messageI18nCode":"sys.validate.email"
}
],
"tableAlign":"left",
"span":24
},
"readonly":false,
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"__vModel__":"email",
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"email",
"placeholder":"请输入邮箱",
"prefixIcon":"",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"filterable":false,
"clearable":true,
"multiple":false,
"fullName":"状态",
"fullNameI18nCode":[
""
],
"props":{
"label":"fullName",
"value":"id"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"Unassigned",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774574965631,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem199e0c",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"static",
"dictionaryType":"",
"tipLabel":"",
"tableFixed":"none",
"label":"状态",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":24
},
"options":[
{
"fullName":"待分配",
"id":"Unassigned"
},
{
"fullName":"跟进中",
"id":"InProgress"
},
{
"fullName":"已转换",
"id":"Converted"
},
{
"fullName":"无效",
"id":"Invalid"
}
],
"__vModel__":"lead_status",
"style":{
"width":"100%"
},
"disabled":false,
"id":"lead_status",
"placeholder":"请选择状态",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"ableIds":[],
"multiple":false,
"fullName":"销售人员",
"fullNameI18nCode":[
""
],
"__config__":{
"formId":"formItem20008f",
"yunzhupaasKey":"usersSelect",
"visibility":[
"pc",
"app"
],
"defaultValue":null,
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"销售人员",
"trigger":"change",
"showLabel":true,
"required":true,
"tableName":"crm_lead",
"renderKey":1774575084782,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-founder",
"defaultCurrent":false,
"tag":"YunzhupaasUsersSelect",
"regList":[],
"tableAlign":"left",
"span":24
},
"__vModel__":"sales_id",
"style":{
"width":"100%"
},
"selectType":"all",
"disabled":false,
"id":"sales_id",
"placeholder":"请选择销售人员",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"filterable":false,
"clearable":true,
"multiple":false,
"fullName":"来源",
"fullNameI18nCode":[
""
],
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774575125146,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem0800c0",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797256993944371205",
"tipLabel":"",
"tableFixed":"none",
"label":"来源",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":24
},
"options":[],
"__vModel__":"customer_source",
"style":{
"width":"100%"
},
"disabled":false,
"id":"customer_source",
"placeholder":"请选择来源",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":200,
"fullName":"备注",
"fullNameI18nCode":[
""
],
"autoSize":{
"minRows":4,
"maxRows":4
},
"showCount":false,
"__config__":{
"formId":"formItem292367",
"yunzhupaasKey":"textarea",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"备注",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_lead",
"renderKey":1774575155402,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-textarea",
"tag":"YunzhupaasTextarea",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"__vModel__":"remark",
"style":{
"width":"100%"
},
"disabled":false,
"id":"remark",
"placeholder":"请输入备注",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
}
]
export default superQueryJson

View File

@@ -0,0 +1,686 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box" v-if="getSearchList.length">
<BasicForm @register="registerSearchForm" :schemas="getSearchList"
@advanced-change="redoHeight" @submit="handleSearchSubmit" @reset="handleSearchReset"
class="search-form">
</BasicForm>
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<BasicTable @register="registerTable" v-bind="getTableBindValue" ref="tableRef"
@columns-change="handleColumnChange">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add" v-auth="'btn_add'"
@click="addHandle()"> {{t('common.add2Text','新增')}}</a-button>
<a-button type="link" preIcon="icon-ym icon-ym-btn-download" v-auth="'btn_download'"
@click="openExportModal(true, { columnList: state.exportList, selectIds: getSelectRowKeys(), showExportSelected: true })"> {{t('common.exportText','导出')}}</a-button>
<a-button type="link" preIcon="icon-ym icon-ym-btn-upload" v-auth="'btn_upload'"
@click="openImportModal(true, { url: 'bcm/CrmLead', menuId: searchInfo.menuId })"> {{t('common.importText','导入')}}</a-button>
</template>
<template #toolbar>
<a-tooltip placement="top">
<template #title>
<span>{{ t('common.superQuery') }}</span>
</template>
<filter-outlined @click="openSuperQuery(true, { columnOptions: superQueryJson })" />
</a-tooltip>
</template>
<template #toolbarAfter>
<ViewList :menuId="route.meta.modelId" :viewList="viewList" @itemClick="handleViewClick" @reload="initViewList" />
<ViewSetting :menuId="route.meta.modelId" :viewList="viewList" :currentView="currentView" @reload="initViewList" />
</template>
<template #bodyCell="{ column, record, index }">
<template v-for="(item, index) in childColumnList" v-if="childColumnList.length">
<template
v-if="column?.id?.includes('-') && item.children && item.children[0] && column.key === item.children[0]?.dataIndex">
<ChildTableColumn :data="record[item.prop]" :head="item.children"
@toggleExpand="toggleExpand(record, item.prop+`Expand`)" @toDetail="toDetail"
:expand="record[item.prop+`Expand`]" :key="index" :showOverflow="true "/>
</template>
</template>
<template v-if="!(record.top || column.id?.includes('-'))">
<template v-if="column.yunzhupaasKey === 'relationForm'">
<p class="link-text"
@click="toDetail(column.modelId, record[column.dataIndex+`_id`], column.propsValue)">
{{ record[column.dataIndex] }}</p>
</template>
<template v-if="column.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="record[column.prop]" :precision="column.precision" :thousands="column.thousands" disabled detailed />
</template>
<template v-if="column.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="record[column.prop]"
:isStorage="column.isStorage"
:precision="column.precision"
:thousands="column.thousands"
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.prop]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.prop]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.prop]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="true"
detailed />
</template>
</template>
<template v-if="column.key === 'action' && !record.top">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form ref="formRef" @reload="reload" />
<Detail ref="detailRef" />
<ExportModal @register="registerExportModal" @download="handleDownload" />
<ImportModal @register="registerImportModal" @reload="reload" />
<PrintSelect @register="registerPrintSelect" @change="handleShowBrowse" />
<PrintBrowse @register="registerPrintBrowse" />
<RelationDetail ref="relationDetailRef" />
<SuperQueryModal @register="registerSuperQueryModal" @superQuery="handleSuperQuery" />
</div>
</template>
<script lang="ts" setup>
import { getList, del, exportData, batchDelete } from './helper/api';
import { getConfigData,getViewList } from '@/api/onlineDev/visualDev';
import { getDictionaryDataSelector } from '@/api/systemData/dictionary';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { getOrgByOrganizeCondition,getDepartmentSelectAsyncList } from '@/api/permission/organize';
import { ref, reactive, onMounted, toRefs, computed, unref, nextTick, toRaw, provide } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useOrganizeStore } from '@/store/modules/organize';
import { useUserStore } from '@/store/modules/user';
import { BasicModal, useModal } from '@/components/Modal';
import { usePopup } from '@/components/Popup';
import { ScrollContainer } from '@/components/Container';
import { BasicLeftTree, TreeActionType } from '@/components/Tree';
import { BasicForm, useForm } from '@/components/Form';
import { BasicTable, useTable, TableAction, ActionItem, TableActionType, SorterResult } from '@/components/Table';
import { SuperQueryModal } from '@/components/CommonModal';
import Form from './Form.vue';
import Detail from './Detail.vue';
// 有关联表单详情:开始
import RelationDetail from '@/views/common/dynamicModel/list/detail/index.vue';
// 有关联表单详情:结束
import ChildTableColumn from '@/views/common/dynamicModel/list/ChildTableColumn.vue';
import { ExportModal } from '@/components/CommonModal';
import { downloadByUrl } from '@/utils/file/download';
import { ImportModal} from '@/components/CommonModal';
// 打印模板多条生成PrintSelect
import PrintSelect from '@/components/PrintDesign/printSelect/index.vue';
import PrintBrowse from '@/components/PrintDesign/printBrowse/index.vue';
import { useRoute,useRouter } from 'vue-router';
import { FilterOutlined } from '@ant-design/icons-vue';
import { getSearchFormSchemas } from '@/components/FormGenerator/src/helper/transform';
import { cloneDeep } from 'lodash-es';
import columnList from './helper/columnList';
import searchList from './helper/searchList';
import superQueryJson from './helper/superQueryJson';
import { dyOptionsList, systemComponentsList } from '@/components/FormGenerator/src/helper/config';
import { thousandsFormat, getParamList} from '@/utils/yunzhupaas';
import { usePermission } from '@/hooks/web/usePermission';
import ViewSetting from '@/views/common/dynamicModel/list/components/ViewSetting.vue';
import ViewList from '@/views/common/dynamicModel/list/components/ViewList.vue';
interface State {
config: any;
columnList: any[];
printListOptions: any[];
columnBtnsList: any[];
customBtnsList: any[];
treeFieldNames: any;
leftTreeData: any[];
leftTreeLoading: boolean;
treeActiveId: string;
treeActiveNodePath: any;
columns: any[];
complexColumns: any[];
childColumnList: any[];
exportList: any[];
cacheList: any[];
currFlow: any;
isCustomCopy: boolean;
candidateType: number;
currRow: any;
workFlowFormData: any;
expandObj: any;
columnSettingList: any[];
searchSchemas: any[];
treeRelationObj: any;
treeQueryJson: any;
leftTreeActiveInfo: any;
keyword: string;
viewList: any[];
currentView: any;
}
const route = useRoute();
const { hasBtnP } = usePermission();
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const organizeStore = useOrganizeStore();
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const [registerExportModal, { openModal: openExportModal, closeModal: closeExportModal, setModalProps: setExportModalProps }] = useModal();
const [registerImportModal, { openModal: openImportModal }] = useModal();
const [registerSuperQueryModal, { openModal: openSuperQuery }] = useModal();
const formRef = ref<any>(null);
const tableRef = ref<Nullable<TableActionType>>(null);
const detailRef = ref<any>(null);
const relationDetailRef = ref<any>(null);
const state = reactive<State>({
config: {},
columnList: [],
printListOptions: [],
columnBtnsList: [],
customBtnsList: [],
treeFieldNames: {
children: 'children' ,
title: 'fullName' ,
key: 'id' ,
isLeaf: 'isLeaf',
},
leftTreeData: [],
leftTreeLoading: false,
treeActiveId: '',
treeActiveNodePath: [],
columns: [],
complexColumns: [], // 复杂表头
childColumnList: [],
exportList: [],
cacheList: [],
currFlow: {},
isCustomCopy: false,
candidateType: 1,
currRow: {},
workFlowFormData: {},
expandObj: {},
columnSettingList: [],
searchSchemas: [],
treeRelationObj: null,
treeQueryJson: {},
leftTreeActiveInfo: {},
keyword: '',
viewList: [],
currentView: {},
});
const defaultSearchInfo = {
menuId: route.meta.modelId as string,
moduleId:'807145376418105157',
superQueryJson: '',
dataType:0,
};
const searchInfo = reactive({
...cloneDeep(defaultSearchInfo),
});
const { childColumnList, searchSchemas, viewList, currentView} = toRefs(state);
const [registerSearchForm, { updateSchema, resetFields, submit: searchFormSubmit, setFieldsValue}] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
});
const [registerTable, { reload, setLoading, getFetchParams, getSelectRows, getSelectRowKeys, redoHeight,clearSelectedRowKeys }] = useTable({
api: getList,
immediate: false,
clickToRowSelect: false,
tableSetting: { setting: false },
afterFetch: (data) => {
const list = data.map((o) => ({
...o,
...state.expandObj,
}));
state.cacheList = cloneDeep(list);
return list;
},
});
const [registerChildTable] = useTable({
pagination: false,
canResize: false,
showTableSetting: false,
});
provide('getLeftTreeActiveInfo', () => state.leftTreeActiveInfo);
const getHasBatchBtn = computed(() => {
let btnsList =[]
btnsList.push('download')
btnsList=btnsList.filter(o => hasBtnP('btn_' + o))
return !!btnsList.length
});
const getColumns = computed(() => {
const columns = state.complexColumns;
return setListValue(state.currentView?.columnList, columns, 'prop');
});
const getSearchList = computed(() => {
const searchSchemas = cloneDeep(state.searchSchemas).map(o => ({ ...o, show: true }));
return setListValue(state.currentView?.searchList, searchSchemas, 'field');
});
const getTableBindValue = computed(() => {
let columns = unref(getColumns);
const defaultSortConfig= [];
const sortField = defaultSortConfig.map(o => (o.sort === 'desc' ? '-' : '') + o.field);
const data: any = {
pagination: { pageSize: 20 }, //有分页
searchInfo: unref(searchInfo),
defSort: { sidx: sortField.join(',') },
sortFn: (sortInfo: SorterResult | SorterResult[]) => {
if (Array.isArray(sortInfo)) {
const sortList = sortInfo.map(o => (o.order === 'descend' ? '-' : '') + o.field);
return { sidx: sortList.join(',') };
} else {
const { field, order } = sortInfo;
if (field && order) {
// 排序字段
return { sidx: (order === 'descend' ? '-' : '') + field };
} else {
return {};
}
}
},
ellipsis:true ,
columns,
bordered: true,
actionColumn: {
width: 150,
title: t('component.table.action'),
dataIndex: 'action',
},
};
if (unref(getHasBatchBtn)) {
const rowSelection: any = { type: 'checkbox' };
data.rowSelection = rowSelection;
}
return data;
});
function init() {
state.config = {};
searchInfo.menuId = route.meta.modelId as string;
state.columnList = columnList;
setLoading(true);
getSearchSchemas();
getColumnList();
initViewList();
nextTick(() => {
unref(getSearchList)?.length ? searchFormSubmit() : reload({ page: 1 });
});
}
function getSearchSchemas() {
const schemas = getSearchFormSchemas(searchList);
state.searchSchemas = schemas;
schemas.forEach((cur) => {
const config = cur.__config__;
if (dyOptionsList.includes(config.yunzhupaasKey)) {
if (config.dataType === 'dictionary') {
if (!config.dictionaryType) return;
getDictionaryDataSelector(config.dictionaryType).then((res) => {
updateSchema([{ field: cur.field, componentProps: { options: res.data.list } }]);
});
}
if (config.dataType === 'dynamic') {
if (!config.propsUrl) return;
const query = { paramList: getParamList(config.templateJson) };
getDataInterfaceRes(config.propsUrl, query).then((res) => {
const data = Array.isArray(res.data) ? res.data : [];
updateSchema([{ field: cur.field, componentProps: { options: data } }]);
});
}
}
cur.defaultValue = cur.value;
});
}
function getColumnList() {
// 开启列表过滤权限
let columnList: any[] = [];
const permissionList = userStore.getPermissionList;
const list = permissionList.filter(o => o.modelId === searchInfo.menuId);
const perColumnList = list[0] && list[0].column ? list[0].column : [];
for (let i = 0; i < state.columnList.length; i++) {
inner: for (let j = 0; j < perColumnList.length; j++) {
if (state.columnList[i].prop === perColumnList[j].enCode) {
columnList.push(state.columnList[i]);
break inner;
}
}
}
state.exportList = columnList;
let columns = columnList.map((o) => ({
...o,
title: o.labelI18nCode ? t(o.labelI18nCode, o.label) : o.label,
dataIndex: o.prop,
align: o.align,
fixed: o.fixed == 'none' ? false : o.fixed,
sorter: o.sortable ? { multiple: 1 } : o.sortable,
width: o.width || 100,
}));
//添加复杂表头
columns = getComplexColumns(columns);
state.columns = columns.filter((o) => o.prop.indexOf('-') < 0);
//子表表头
getChildComplexColumns(columns);
}
//复杂表头
function getComplexColumns(columns) {
//这里生成复杂表头的配置
let complexHeaderList: any[] = [];
if (!complexHeaderList.length) return columns;
let childColumns: any[] = [];
let firstChildColumns: string[] = [];
for (let i = 0; i < complexHeaderList.length; i++) {
const e = complexHeaderList[i];
e.label = e.fullName;
e.labelI18nCode = e.fullNameI18nCode;
e.title = e.fullNameI18nCode ? t(e.fullNameI18nCode, e.fullName) : e.fullName;
e.align = e.align;
e.dataIndex = e.id;
e.prop = e.id;
e.children = [];
e.yunzhupaasKey = 'complexHeader';
if (e.childColumns?.length) {
childColumns.push(...e.childColumns);
for (let k = 0; k < e.childColumns.length; k++) {
const item = e.childColumns[k];
for (let j = 0; j < columns.length; j++) {
const o = columns[j];
if (o.prop == item && o.fixed !== 'left' && o.fixed !== 'right') e.children.push({ ...o });
}
}
}
if (e.children.length) firstChildColumns.push(e.children[0].prop);
}
complexHeaderList = complexHeaderList.filter(o => o.children.length);
let list: any[] = [];
for (let i = 0; i < columns.length; i++) {
const e = columns[i];
if (!childColumns.includes(e.prop)) {
list.push(e);
} else {
if (firstChildColumns.includes(e.prop)) {
const item = complexHeaderList.find(o => o.childColumns.includes(e.prop));
list.push(item);
}
}
}
return list;
}
//子表表头
function getChildComplexColumns(columnList) {
let list: any[] = [];
for (let i = 0; i < columnList.length; i++) {
const e = columnList[i];
if (!e.prop.includes('-')) {
list.push(e);
} else {
let prop = e.prop.split('-')[0];
let vModel = e.prop.split('-')[1];
let label = e.label.split('-')[0];
let childLabel = e.label.replace(label + '-', '');
if (e.fullNameI18nCode && Array.isArray(e.fullNameI18nCode) && e.fullNameI18nCode[0]) label = t(e.fullNameI18nCode[0], label);
let newItem = {
align: 'center',
yunzhupaasKey: 'table',
prop,
label,
title: label,
dataIndex: prop,
children: [],
};
e.dataIndex = vModel;
e.title = e.labelI18nCode ? t(e.labelI18nCode, childLabel) : childLabel;
if (!state.expandObj.hasOwnProperty(prop+`Expand`)) state.expandObj[prop+`Expand`] = false;
if (!list.some((o) => o.prop === prop)) list.push(newItem);
for (let i = 0; i < list.length; i++) {
if (list[i].prop === prop) {
list[i].children.push(e);
break;
}
}
}
}
// 行内分组展示
getMergeList(list);
state.complexColumns = list;
state.childColumnList = list.filter((o) => o.yunzhupaasKey === 'table');
// 子表分组展示宽度取100
for (let i = 0; i < state.childColumnList.length; i++) {
const e = state.childColumnList[i];
if (e.children?.length) e.children = e.children.map(o => ({ ...o, width: 100 }));
}
}
function getMergeList(list) {
list.forEach((item) => {
if (item.yunzhupaasKey === 'table' && item.children && item.children.length) {
item.children.forEach((child, index) => {
if (index == 0) {
child.customCell = () => ({
rowspan: 1,
colspan: item.children.length,
class: 'child-table-box',
});
} else {
child.customCell = () => ({
rowspan: 0,
colspan: 0,
});
}
});
}
});
}
function toggleExpand(row, field) {
row[field] = !row[field];
}
// 关联表单查看详情
function toDetail(modelId, id, propsValue) {
if (!id) return;
getConfigData(modelId).then((res) => {
if (!res.data || !res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = 'general';
formConf.hasPrintBtn = false;
formConf.customBtns = [];
const data = { id, formConf, modelId, propsValue};
relationDetailRef.value?.init(data);
});
}
function handleColumnChange(data) {
state.columnSettingList = data;
}
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText','编辑') ,
onClick: updateHandle.bind(null, record),
auth: 'btn_edit', //有按钮权限
},
{
label: t('common.delText','删除') ,
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
auth: 'btn_remove', //有按钮权限
},
{
label: t('common.detailText','详情') ,
onClick: goDetail.bind(null, record),
auth: 'btn_detail', //有按钮权限
},
];
}
// 编辑
function updateHandle(record) {
// 不带工作流
const data = {
id: record.id,
menuId: searchInfo.menuId,
allList: state.cacheList,
};
formRef.value?.init(data);
}
// 删除
function handleDelete(id) {
const query={ids:[id] }
batchDelete(query).then((res) => {
createMessage.success(res.msg);
clearSelectedRowKeys();
reload();
});
}
// 查看详情
function goDetail(record) {
// 不带流程
const data = {
id: record.id,
};
detailRef.value?.init(data);
}
// 新增
function addHandle() {
// 不带流程新增
const data = {
id: '',
menuId: searchInfo.menuId,
allList: state.cacheList,
};
formRef.value?.init(data);
}
// 导出
function handleDownload(data) {
let query = { ...getFetchParams(), ...data };
exportData(query)
.then((res) => {
setExportModalProps({ confirmLoading: false });
if (!res.data.url) return;
downloadByUrl({ url: res.data.url });
closeExportModal();
})
.catch(() => {
setExportModalProps({ confirmLoading: false });
});
}
// 高级查询
function handleSuperQuery(superQueryJson) {
searchInfo.superQueryJson = superQueryJson;
reload({ page: 1 });
}
function handleSearchReset() {
clearSelectedRowKeys();
if (!state.resetFromTree) updateSearchFormValue();
if (state.resetFromTree) state.resetFromTree = false;
}
function handleSearchSubmit(data) {
clearSelectedRowKeys();
let obj = {
...defaultSearchInfo,
superQueryJson: searchInfo.superQueryJson,
...data,
};
Object.keys(searchInfo).map(key => {
delete searchInfo[key];
});
for (let [key, value] of Object.entries(obj)) {
searchInfo[key.replaceAll('-', '_')] = value;
}
console.log(searchInfo);
reload({ page: 1 });
}
function updateSearchFormValue() {
if (!state.treeActiveId) return searchFormSubmit();
let queryJson: any = {};
let leftTreeActiveInfo: any = {};
const isMultiple = !state.treeRelationObj ? false : state.treeRelationObj.searchMultiple;
//多级左侧树,需要拼父级->转为查询参数
if (state.treeRelationObj && state.treeRelationObj.yunzhupaasKey && ['organizeSelect', 'cascader', 'areaSelect'].includes(state.treeRelationObj.yunzhupaasKey)) {
let currValue = [];
currValue = state.treeActiveNodePath.map(o => o[state.treeFieldNames.key]);
queryJson = { '': isMultiple ? [currValue] : currValue };
leftTreeActiveInfo = { '': state.treeRelationObj.multiple ? [currValue] : currValue };
} else {
queryJson = { '': isMultiple ? [state.treeActiveId] : state.treeActiveId };
leftTreeActiveInfo = { '': state.treeRelationObj.multiple ? [state.treeActiveId] : state.treeActiveId };
}
state.leftTreeActiveInfo = leftTreeActiveInfo;
if(unref(getSearchList)?.length){
// 有搜索列表
setFieldsValue(queryJson);
searchFormSubmit();
}else{
// 无搜索列表
handleSearchSubmit(queryJson);
}
}
function initViewList(currentId = '') {
const query = {
menuId: route.meta.modelId,
};
getViewList(query).then(res => {
const columns : any[]= state.complexColumns;
const searchList: any[] = state.searchSchemas.map(o => ({ label: o.label, id: o.field, show: o.show, labelI18nCode: o.labelI18nCode }));
const columnList: any[] = columns.map(o => ({ label: o.label, id: o.prop, show: true, fixed: o.fixed || 'none', labelI18nCode: o.labelI18nCode }));
state.viewList = (res.data || []).map(o => {
if (o.type == 0) return { ...o, searchList, columnList };
return { ...o, searchList: o.searchList ? JSON.parse(o.searchList) : [], columnList: o.columnList ? JSON.parse(o.columnList) : [] };
});
if (currentId) {
state.currentView = state.viewList.filter(o => o.id === currentId)[0] || state.viewList[0];
} else {
state.currentView = state.viewList.filter(o => o.status === 1)[0] || state.viewList[0];
}
});
}
function handleViewClick(item) {
state.currentView = item;
}
function setListValue(data: any[] = [], defaultData: any[] = [], key) {
let list: any[] = [];
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < defaultData.length; j++) {
if (data[i].show && data[i].id == defaultData[j][key]) list.push(defaultData[j]);
}
}
return list;
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,295 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :title="title" destroyOnClose>
<template #insertToolbar> </template>
<a-row class="p-10px dynamic-form" :style="{ margin: '0 auto', width: '100%' }">
<!-- 表单 -->
<a-form :colon="false" size="middle" layout="horizontal" labelAlign="right" :labelCol="{ style: { width: '100px' } }" :model="dataForm" ref="formRef">
<a-row :gutter="15">
<!-- 具体表单 -->
<a-col :span="12" class="ant-col-item">
<a-form-item name="opportunity_code">
<template #label>商机编码 </template>
<YunzhupaasInput
v-model:value="dataForm.opportunity_code"
placeholder="请输入商机编码"
:maxlength="50"
disabled
detailed
allowClear
:style="{ width: '100%' }"
:maskConfig="maskConfig.opportunity_code">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="opportunity_name">
<template #label>商机名称 </template>
<YunzhupaasInput
v-model:value="dataForm.opportunity_name"
placeholder="请输入商机名称"
:maxlength="50"
disabled
detailed
allowClear
:style="{ width: '100%' }"
:maskConfig="maskConfig.opportunity_name">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="customer_id">
<template #label>客户 </template>
<p class="link-text leading-32px" @click="toDetail('806858527036409349', dataForm.customer_id_id, 'company_id')">{{ dataForm.customer_id }}</p>
<ExtraRelationInfo
:extraOptions="state.extraOptions.customer_id"
:data="state.extraData.customer_id"
v-if="state.extraOptions.customer_id?.length && state.extraData.customer_id && JSON.stringify(state.extraData.customer_id) !== '{}'" />
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="owner_id">
<template #label>商机负责人 </template> <p>{{ dataForm.owner_id }}</p>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="opportunity_stage">
<template #label>商机阶段 </template> <p>{{ dataForm.opportunity_stage }}</p>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="amount">
<template #label>预计金额 </template>
<YunzhupaasInputNumber
v-model:value="dataForm.amount"
placeholder="请输入预计金额"
disabled
detailed
:style="{ width: '100%' }"
:max="9999999"
:step="1"
:precision="2"
:controls="false"
addonAfter="元">
</YunzhupaasInputNumber>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="close_date">
<template #label>预计成交日期 </template> <p>{{ dataForm.close_date }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="desciption">
<template #label>商机简介 </template> <p>{{ dataForm.desciption }}</p>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="probability">
<template #label>赢单概率 </template>
<YunzhupaasInputNumber
v-model:value="dataForm.probability"
placeholder="请输入赢单概率"
disabled
detailed
:style="{ width: '100%' }"
:max="100"
:step="1"
:precision="2"
:controls="false"
addonAfter="%">
</YunzhupaasInputNumber>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="last_followup_date">
<template #label>最后跟进日期 </template> <p>{{ dataForm.last_followup_date }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="competitor">
<template #label>竞争对手信息 </template> <p>{{ dataForm.competitor }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="analysis">
<template #label>决策分析 </template> <p>{{ dataForm.analysis }}</p>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="remark">
<template #label>备注 </template> <p>{{ dataForm.remark }}</p>
</a-form-item>
</a-col>
<!-- 表单结束 -->
</a-row>
</a-form>
</a-row>
</BasicPopup>
<!-- 有关联表单详情开始 -->
<RelationDetail ref="relationDetailRef" />
<!-- 有关联表单详情结束 -->
</template>
<script lang="ts" setup>
import { getDetailInfo } from './helper/api';
import { getConfigData } from '@/api/onlineDev/visualDev';
import { reactive, toRefs, nextTick, ref, computed, unref, toRaw } from 'vue';
import { BasicPopup, usePopup } from '@/components/Popup';
import { BasicModal, useModal } from '@/components/Modal';
// 有关联表单详情
import RelationDetail from '@/views/common/dynamicModel/list/detail/index.vue';
// 表单权限
import { usePermission } from '@/hooks/web/usePermission';
import { useMessage } from '@/hooks/web/useMessage';
import { CaretRightOutlined } from '@ant-design/icons-vue';
import { buildUUID } from '@/utils/uuid';
import { useI18n } from '@/hooks/web/useI18n';
import { getDataChange } from '@/api/onlineDev/visualDev';
import { getDataInterfaceDataInfoByIds } from '@/api/systemData/dataInterface';
import ExtraRelationInfo from '@/components/yunzhupaas/RelationForm/src/ExtraRelationInfo.vue';
interface State {
dataForm: any;
title: string;
maskConfig: any;
interfaceRes: any;
locationScope: any;
extraOptions: any;
extraData: any;
}
defineOptions({ name: 'Detail' });
const { createMessage, createConfirm } = useMessage();
const [registerPopup, { openPopup, setPopupProps, closePopup }] = usePopup();
const { t } = useI18n();
const relationDetailRef = ref<any>(null);
const state = reactive<State>({
dataForm: {},
title: t('common.detailText', '详情'),
maskConfig: {
opportunity_code: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
opportunity_name: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
},
interfaceRes: {
desciption: [],
amount: [],
close_date: [],
opportunity_code: [],
owner_id: [],
probability: [],
remark: [],
analysis: [],
last_followup_date: [],
opportunity_stage: [],
opportunity_name: [],
competitor: [],
customer_id: [],
},
locationScope: {},
extraOptions: {
customer_id: [],
},
extraData: {
customer_id: {},
},
});
const { title, dataForm, maskConfig } = toRefs(state);
// 表单权限
const { hasFormP } = usePermission();
defineExpose({ init });
function init(data) {
state.dataForm.id = data.id;
openPopup();
nextTick(() => {
setTimeout(initData, 0);
});
}
function initData() {
changeLoading(true);
if (state.dataForm.id) {
getData(state.dataForm.id);
} else {
closePopup();
}
}
function getData(id) {
getDetailInfo(id).then(res => {
state.dataForm = res.data || {};
nextTick(() => {
getcustomer_idExtraInfo();
changeLoading(false);
});
});
}
function toDetail(modelId, id, propsValue) {
if (!id) return;
getConfigData(modelId).then(res => {
if (!res.data || !res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = 'general';
formConf.hasPrintBtn = false;
formConf.customBtns = [];
const data = { id, formConf, modelId, propsValue };
relationDetailRef.value?.init(data);
});
}
function setFormProps(data) {
setPopupProps(data);
}
function changeLoading(loading) {
setFormProps({ loading });
}
function getParamList(key) {
let templateJson: any[] = state.interfaceRes[key];
if (!templateJson || !templateJson.length || !state.dataForm) return templateJson;
for (let i = 0; i < templateJson.length; i++) {
if (templateJson[i].relationField && templateJson[i].sourceType == 1) {
templateJson[i].defaultValue = state.dataForm[templateJson[i].relationField + '_id'] || '';
}
}
return templateJson;
}
function getcustomer_idExtraInfo() {
if (!state.dataForm.customer_id) return;
console.log(state.dataForm.customer_id_id);
let query: any = {
id: state.dataForm.customer_id_id,
propsValue: 'company_id',
};
getDataChange('806858527036409349', query).then(res => {
if (!res.data || !res.data.data) return;
const data = JSON.parse(res.data.data);
state.extraData.customer_id = data;
});
}
</script>

View File

@@ -0,0 +1,647 @@
<template>
<BasicPopup
v-bind="$attrs"
@register="registerPopup"
showOkBtn
destroyOnClose
:cancelText="t('common.cancelText', '取消')"
:okText="t('common.okText', '确定')"
@ok="handleSubmit"
:closeFunc="onClose">
<template #title>
<a-space :size="10">
<div class="text-16px font-medium">{{ title }}</div>
<a-space-compact size="small" block v-if="dataForm.id">
<a-tooltip :title="t('common.prevRecord')">
<a-button size="small" :disabled="getPrevDisabled" @click="handlePrev">
<i class="icon-ym icon-ym-caret-left text-10px"></i>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.nextRecord')">
<a-button size="small" :disabled="getNextDisabled" @click="handleNext">
<i class="icon-ym icon-ym-caret-right text-10px"></i>
</a-button>
</a-tooltip>
</a-space-compact>
</a-space>
</template>
<template #insertToolbar>
<yunzhupaasCheckboxSingle v-model:value="submitType" :label="continueText" v-if="showContinueBtn" />
</template>
<a-row class="p-10px dynamic-form" :style="{ margin: '0 auto', width: '100%' }">
<a-form
:colon="false"
size="middle"
layout="horizontal"
labelAlign="right"
:labelCol="{ style: { width: '100px' } }"
:model="dataForm"
:rules="dataRule"
ref="formRef">
<a-row :gutter="15">
<!-- 具体表单 -->
<a-col :span="12" class="ant-col-item">
<a-form-item name="opportunity_code">
<template #label>商机编码</template>
<YunzhupaasInput
v-model:value="dataForm.opportunity_code"
@change="changeData('opportunity_code', -1)"
placeholder="请输入商机编码"
:maxlength="50"
:allowClear="true"
:style="{ width: '100%' }"
:maskConfig="maskConfig.opportunity_code"
:showCount="false">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="opportunity_name">
<template #label>商机名称 </template>
<YunzhupaasInput
v-model:value="dataForm.opportunity_name"
@change="changeData('opportunity_name', -1)"
placeholder="请输入商机名称"
:maxlength="50"
:allowClear="true"
:style="{ width: '100%' }"
:maskConfig="maskConfig.opportunity_name"
:showCount="false">
</YunzhupaasInput>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="customer_id">
<template #label>客户 </template>
<YunzhupaasRelationForm
v-model:value="dataForm.customer_id"
@change="changeData('customer_id', -1)"
placeholder="请选择客户"
:templateJson="state.interfaceRes.customer_id"
:allowClear="true"
:style="{ width: '100%' }"
:showSearch="false"
:field="'customer_id'"
modelId="806858527036409349"
:columnOptions="optionsObj.customer_idcolumnOptions"
relationField="company_name"
popupWidth="70%"
propsValue="company_id"
:queryType="0"
:extraOptions="state.extraOptions.customer_id">
</YunzhupaasRelationForm>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="owner_id">
<template #label>商机负责人 </template>
<YunzhupaasUsersSelect
v-model:value="dataForm.owner_id"
@change="changeData('owner_id', -1)"
placeholder="请选择商机负责人"
:allowClear="true"
:style="{ width: '100%' }"
selectType="all">
</YunzhupaasUsersSelect>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="opportunity_stage">
<template #label>商机阶段 </template>
<YunzhupaasSelect
v-model:value="dataForm.opportunity_stage"
@change="changeData('opportunity_stage', -1)"
placeholder="请选择商机阶段"
:templateJson="state.interfaceRes.opportunity_stage"
:allowClear="true"
:style="{ width: '100%' }"
:showSearch="false"
:options="optionsObj.opportunity_stageOptions"
:fieldNames="optionsObj.opportunity_stageProps">
</YunzhupaasSelect>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="amount">
<template #label>预计金额 </template>
<YunzhupaasInputNumber
v-model:value="dataForm.amount"
@change="changeData('amount', -1)"
placeholder="请输入预计金额"
:style="{ width: '100%' }"
:max="9999999"
:step="1"
:precision="2"
addonAfter="元"
:controls="false">
</YunzhupaasInputNumber>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="close_date">
<template #label>预计成交日期 </template>
<YunzhupaasDatePicker
v-model:value="dataForm.close_date"
@change="changeData('close_date', -1)"
placeholder="请选择预计成交日期"
:allowClear="true"
:style="{ width: '100%' }"
format="yyyy-MM-dd"
:startTime="getRelationDate(false, 1, 1, '', '')"
:endTime="getRelationDate(false, 1, 1, '', '')">
</YunzhupaasDatePicker>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="desciption">
<template #label>商机简介 </template>
<YunzhupaasTextarea
v-model:value="dataForm.desciption"
@change="changeData('desciption', -1)"
placeholder="请输入商机简介"
:maxlength="200"
:allowClear="true"
:style="{ width: '100%' }"
:autoSize="{ minRows: 3, maxRows: 3 }"
:showCount="false">
</YunzhupaasTextarea>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="probability">
<template #label>赢单概率 </template>
<YunzhupaasInputNumber
v-model:value="dataForm.probability"
@change="changeData('probability', -1)"
placeholder="请输入赢单概率"
:style="{ width: '100%' }"
:max="100"
:step="1"
:precision="2"
addonAfter="%"
:controls="false">
</YunzhupaasInputNumber>
</a-form-item>
</a-col>
<a-col :span="12" class="ant-col-item">
<a-form-item name="last_followup_date">
<template #label>最后跟进日期 </template>
<YunzhupaasDatePicker
v-model:value="dataForm.last_followup_date"
@change="changeData('last_followup_date', -1)"
placeholder="请选择最后跟进日期"
:disabled="true"
:allowClear="true"
:style="{ width: '100%' }"
format="yyyy-MM-dd"
:startTime="getRelationDate(false, 1, 1, '', '')"
:endTime="getRelationDate(false, 1, 1, '', '')">
</YunzhupaasDatePicker>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="competitor">
<template #label>竞争对手信息 </template>
<YunzhupaasTextarea
v-model:value="dataForm.competitor"
@change="changeData('competitor', -1)"
placeholder="请输入竞争对手信息"
:maxlength="200"
:allowClear="true"
:style="{ width: '100%' }"
:autoSize="{ minRows: 3, maxRows: 3 }"
:showCount="false">
</YunzhupaasTextarea>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="analysis">
<template #label>决策分析 </template>
<YunzhupaasTextarea
v-model:value="dataForm.analysis"
@change="changeData('analysis', -1)"
placeholder="请输入决策分析"
:maxlength="200"
:allowClear="true"
:style="{ width: '100%' }"
:autoSize="{ minRows: 3, maxRows: 3 }"
:showCount="false">
</YunzhupaasTextarea>
</a-form-item>
</a-col>
<a-col :span="24" class="ant-col-item">
<a-form-item name="remark">
<template #label>备注 </template>
<YunzhupaasTextarea
v-model:value="dataForm.remark"
@change="changeData('remark', -1)"
placeholder="请输入备注"
:maxlength="200"
:allowClear="true"
:style="{ width: '100%' }"
:autoSize="{ minRows: 3, maxRows: 3 }"
:showCount="false">
</YunzhupaasTextarea>
</a-form-item>
</a-col>
<!-- 表单结束 -->
</a-row>
</a-form>
</a-row>
</BasicPopup>
</template>
<script lang="ts" setup>
import { create, update, getInfo } from './helper/api';
import { reactive, toRefs, nextTick, ref, unref, computed, toRaw, inject } from 'vue';
import { BasicPopup, usePopup } from '@/components/Popup';
import { YunzhupaasRelationForm } from '@/components/yunzhupaas';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from '@/store/modules/user';
import type { FormInstance } from 'ant-design-vue';
import { thousandsFormat, getDateTimeUnit, getTimeUnit } from '@/utils/yunzhupaas';
import { getDictionaryDataSelector } from '@/api/systemData/dictionary';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import dayjs from 'dayjs';
// 表单权限
import { usePermission } from '@/hooks/web/usePermission';
import { cloneDeep } from 'lodash-es';
import { buildUUID } from '@/utils/uuid';
import { CaretRightOutlined } from '@ant-design/icons-vue';
interface State {
dataForm: any;
tableRows: any;
dataRule: any;
optionsObj: any;
childIndex: any;
isEdit: any;
interfaceRes: any;
//可选范围默认值
ableAll: any;
//掩码配置
maskConfig: any;
//定位属性
locationScope: any;
extraOptions: any;
title: string;
continueText: string;
allList: any[];
currIndex: number;
isContinue: boolean;
submitType: number;
showContinueBtn: boolean;
}
const emit = defineEmits(['reload']);
const getLeftTreeActiveInfo: (() => any) | null = inject('getLeftTreeActiveInfo', null);
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const [registerPopup, { openPopup, setPopupProps }] = usePopup();
const formRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
opportunity_code: undefined,
opportunity_name: undefined,
customer_id: '',
owner_id: userInfo.userId ? userInfo.userId : '',
opportunity_stage: '',
amount: undefined,
close_date: undefined,
desciption: undefined,
probability: undefined,
last_followup_date: dayjs().startOf(getDateTimeUnit('yyyy-MM-dd')).valueOf(),
competitor: undefined,
analysis: undefined,
remark: undefined,
version: 0,
},
tableRows: {},
dataRule: {
customer_id: [
{
required: true,
message: t('sys.validate.textRequiredSuffix', '不能为空'),
trigger: 'blur',
},
],
opportunity_code: [
{
required: true,
message: t('sys.validate.textRequiredSuffix', '不能为空'),
trigger: 'blur',
},
],
opportunity_name: [
{
required: true,
message: t('sys.validate.textRequiredSuffix', '不能为空'),
trigger: 'blur',
},
],
owner_id: [
{
required: true,
message: t('sys.validate.textRequiredSuffix', '不能为空'),
trigger: 'blur',
},
],
opportunity_stage: [
{
required: true,
message: t('sys.validate.textRequiredSuffix', '不能为空'),
trigger: 'blur',
},
],
},
optionsObj: {
customer_idcolumnOptions: [
{ label: '企业名称', value: 'company_name' },
{ label: '类型', value: 'entity_type' },
{ label: '归属组织', value: 'org_id' },
{ label: '客户负责人', value: 'owner_id' },
{ label: '客户等级', value: 'customer_level' },
],
opportunity_stageOptions: [],
opportunity_stageProps: { label: 'fullName', value: 'enCode' },
},
childIndex: -1,
isEdit: false,
interfaceRes: {
desciption: [],
amount: [],
close_date: [],
opportunity_code: [],
owner_id: [],
probability: [],
remark: [],
analysis: [],
last_followup_date: [],
opportunity_stage: [],
opportunity_name: [],
competitor: [],
customer_id: [],
},
//可选范围默认值
ableAll: {},
//掩码配置
maskConfig: {
opportunity_code: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
opportunity_name: {
prefixType: 1,
useUnrealMask: false,
maskType: 1,
unrealMaskLength: 1,
prefixLimit: 0,
suffixLimit: 0,
filler: '*',
prefixSpecifyChar: '',
suffixType: 1,
ignoreChar: '',
suffixSpecifyChar: '',
},
},
//定位属性
locationScope: {},
extraOptions: {
customer_id: [],
},
title: '',
continueText: '',
allList: [],
currIndex: 0,
isContinue: false,
submitType: 0,
showContinueBtn: true,
});
const { title, continueText, showContinueBtn, dataRule, dataForm, optionsObj, ableAll, maskConfig, submitType } = toRefs(state);
const getPrevDisabled = computed(() => state.currIndex === 0);
const getNextDisabled = computed(() => state.currIndex === state.allList.length - 1);
// 表单权限
const { hasFormP } = usePermission();
defineExpose({ init });
function init(data) {
state.submitType = 0;
state.isContinue = false;
state.title = !data.id ? t('common.add2Text', '新增') : t('common.editText', '编辑');
state.continueText = !data.id ? t('common.continueAndAddText', '确定并新增') : t('common.continueText', '确定并继续');
setFormProps({ continueLoading: false });
state.dataForm.id = data.id;
openPopup();
state.allList = data.allList;
state.currIndex = state.allList.length && data.id ? state.allList.findIndex(item => item.id === data.id) : 0;
nextTick(() => {
getForm().resetFields();
setTimeout(initData, 0);
});
}
function initData() {
changeLoading(true);
if (state.dataForm.id) {
getData(state.dataForm.id);
} else {
//初始化options
getopportunity_stageOptions();
// 设置默认值
state.dataForm = {
opportunity_code: undefined,
opportunity_name: undefined,
customer_id: '',
owner_id: userInfo.userId ? userInfo.userId : '',
opportunity_stage: '',
amount: undefined,
close_date: undefined,
desciption: undefined,
probability: undefined,
last_followup_date: dayjs().startOf(getDateTimeUnit('yyyy-MM-dd')).valueOf(),
competitor: undefined,
analysis: undefined,
remark: undefined,
version: 0,
};
if (getLeftTreeActiveInfo) state.dataForm = { ...state.dataForm, ...(getLeftTreeActiveInfo() || {}) };
state.childIndex = -1;
changeLoading(false);
}
}
function getForm() {
const form = unref(formRef);
if (!form) {
throw new Error('form is null!');
}
return form;
}
function getData(id) {
getInfo(id).then(res => {
state.dataForm = res.data || {};
getopportunity_stageOptions();
state.childIndex = -1;
changeLoading(false);
});
}
async function handleSubmit(type) {
try {
const values = await getForm()?.validate();
if (!values) return;
setFormProps({ confirmLoading: true });
const formMethod = state.dataForm.id ? update : create;
formMethod(state.dataForm)
.then(res => {
createMessage.success(res.msg);
setFormProps({ confirmLoading: false });
if (state.submitType == 1) {
initData();
state.isContinue = true;
} else {
setFormProps({ open: false });
emit('reload');
}
})
.catch(() => {
setFormProps({ confirmLoading: false });
});
} catch (_) {}
}
function handlePrev() {
state.currIndex--;
handleGetNewInfo();
}
function handleNext() {
state.currIndex++;
handleGetNewInfo();
}
function handleGetNewInfo() {
changeLoading(true);
getForm().resetFields();
const id = state.allList[state.currIndex].id;
getData(id);
}
function setFormProps(data) {
setPopupProps(data);
}
function changeLoading(loading) {
setPopupProps({ loading });
}
async function onClose() {
if (state.isContinue) emit('reload');
return true;
}
function changeData(model, index) {
state.isEdit = false;
state.childIndex = index;
for (let key in state.interfaceRes) {
if (key != model) {
let faceReList = state.interfaceRes[key];
for (let i = 0; i < faceReList.length; i++) {
let relationField = faceReList[i].relationField;
if (relationField) {
let modelAll = relationField.split('-');
let faceMode = '';
let faceMode2 = modelAll.length == 2 ? modelAll[0].substring(0, modelAll[0].length - 4) + modelAll[1] : '';
for (let i = 0; i < modelAll.length; i++) {
faceMode += modelAll[i];
}
if (faceMode == model || faceMode2 == model) {
let options = 'get' + key + 'Options';
eval(options)(true);
changeData(key, index);
}
}
}
}
}
}
function changeDataFormData(type, data, model, index, defaultValue) {
if (!state.isEdit) {
if (type == 2) {
for (let i = 0; i < state.dataForm[data].length; i++) {
if (index == -1) {
state.dataForm[data][i][model] = defaultValue;
} else if (index == i) {
state.dataForm[data][i][model] = defaultValue;
}
}
} else {
state.dataForm[data] = defaultValue;
}
}
}
//数据选项--数据字典初始化方法
function getopportunity_stageOptions() {
getDictionaryDataSelector('797444616478523397').then(res => {
state.optionsObj.opportunity_stageOptions = res.data.list;
});
}
function getRelationDate(timeRule, timeType, timeTarget, timeValueData, dataValue) {
let timeDataValue: any = null;
let timeValue = Number(timeValueData);
if (timeRule) {
if (timeType == 1) {
timeDataValue = timeValue;
} else if (timeType == 2) {
timeDataValue = dataValue;
} else if (timeType == 3) {
timeDataValue = new Date().getTime();
} else if (timeType == 4 || timeType == 5) {
const type = getTimeUnit(timeTarget);
const method = timeType == 4 ? 'subtract' : 'add';
timeDataValue = dayjs()[method](timeValue, type).valueOf();
}
}
return timeDataValue;
}
function getRelationTime(timeRule, timeType, timeTarget, timeValue, formatType, dataValue) {
let format = formatType == 'HH:mm' ? 'HH:mm:00' : formatType;
let timeDataValue: any = null;
if (timeRule) {
if (timeType == 1) {
timeDataValue = timeValue || '00:00:00';
if (timeDataValue.split(':').length == 3) {
timeDataValue = timeDataValue;
} else {
timeDataValue = timeDataValue + ':00';
}
} else if (timeType == 2) {
timeDataValue = dataValue;
} else if (timeType == 3) {
timeDataValue = dayjs().format(format);
} else if (timeType == 4 || timeType == 5) {
const type = getTimeUnit(timeTarget + 3);
const method = timeType == 4 ? 'subtract' : 'add';
timeDataValue = dayjs()[method](timeValue, type).format(format);
}
}
return timeDataValue;
}
</script>

View File

@@ -0,0 +1,34 @@
import { defHttp } from '@/utils/http/axios';
// 获取列表
export function getList(data) {
return defHttp.post({ url: '/api/bcm/CrmOpportunity/getList', data });
}
// 新建
export function create(data) {
return defHttp.post({ url:'/api/bcm/CrmOpportunity', data });
}
// 修改
export function update(data) {
return defHttp.put({ url: '/api/bcm/CrmOpportunity/'+ data.id, data });
}
// 详情(无转换数据)
export function getInfo(id) {
return defHttp.get({ url: '/api/bcm/CrmOpportunity/' + id });
}
// 获取(转换数据)
export function getDetailInfo(id) {
return defHttp.get({ url: '/api/bcm/CrmOpportunity/detail/' + id });
}
// 删除
export function del(id) {
return defHttp.delete({ url: '/api/bcm/CrmOpportunity/' + id });
}
// 批量删除数据
export function batchDelete(data) {
return defHttp.delete({ url: '/api/bcm/CrmOpportunity/batchRemove', data });
}
// 导出
export function exportData(data) {
return defHttp.post({ url: '/api/bcm/CrmOpportunity/Actions/Export', data });
}

View File

@@ -0,0 +1,619 @@
const columnList = [
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItemccf93a",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机编码",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575485596,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":12
},
"readonly":false,
"prop":"opportunity_code",
"__vModel__":"opportunity_code",
"disabled":false,
"id":"opportunity_code",
"placeholder":"请输入商机编码",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"商机编码",
"label":"商机编码",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItemd62246",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机名称",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575491289,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":12
},
"readonly":false,
"prop":"opportunity_name",
"__vModel__":"opportunity_name",
"disabled":false,
"id":"opportunity_name",
"placeholder":"请输入商机名称",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"商机名称",
"label":"商机名称",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"popupType":"drawer",
"yunzhupaasKey":"relationForm",
"hasPage":false,
"modelId":"806858527036409349",
"pageSize":20,
"columnOptions":[
{
"label":"企业名称",
"value":"company_name"
},
{
"label":"类型",
"value":"entity_type"
},
{
"label":"归属组织",
"value":"org_id"
},
{
"label":"客户负责人",
"value":"owner_id"
},
{
"label":"客户等级",
"value":"customer_level"
}
],
"fullNameI18nCode":[
""
],
"align":"left",
"__config__":{
"formId":"formItemd16307",
"yunzhupaasKey":"relationForm",
"visibility":[
"pc",
"app"
],
"defaultValue":"",
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"客户",
"trigger":"change",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575610653,
"layout":"colFormItem",
"transferList":[],
"tagIcon":"icon-ym icon-ym-generator-menu",
"tag":"YunzhupaasRelationForm",
"regList":[],
"tableAlign":"left",
"span":24
},
"prop":"customer_id",
"__vModel__":"customer_id",
"disabled":false,
"id":"customer_id",
"placeholder":"请选择客户",
"popupWidth":"70%",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"filterable":false,
"clearable":true,
"resizable":true,
"fullName":"客户",
"label":"客户",
"sortable":false,
"relationField":"company_name",
"queryType":0,
"extraOptions":[],
"popupTitle":"选择客户",
"width":null,
"fixed":"none",
"style":{
"width":"100%"
},
"labelI18nCode":"",
"propsValue":"company_id"
},
{
"yunzhupaasKey":"usersSelect",
"clearable":true,
"resizable":true,
"ableIds":[],
"multiple":false,
"fullName":"商机负责人",
"fullNameI18nCode":[
""
],
"label":"商机负责人",
"sortable":false,
"align":"left",
"__config__":{
"formId":"formItem133e27",
"yunzhupaasKey":"usersSelect",
"visibility":[
"pc",
"app"
],
"defaultValue":"",
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机负责人",
"trigger":"change",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575984909,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-founder",
"defaultCurrent":true,
"tag":"YunzhupaasUsersSelect",
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"owner_id",
"width":null,
"__vModel__":"owner_id",
"fixed":"none",
"style":{
"width":"100%"
},
"selectType":"all",
"disabled":false,
"id":"owner_id",
"placeholder":"请选择商机负责人",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"商机阶段",
"fullNameI18nCode":[
""
],
"label":"商机阶段",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576009744,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem97b95a",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797444616478523397",
"tipLabel":"",
"tableFixed":"none",
"label":"商机阶段",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"opportunity_stage",
"width":null,
"options":[],
"__vModel__":"opportunity_stage",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"opportunity_stage",
"placeholder":"请选择商机阶段",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"inputNumber",
"controls":false,
"precision":2,
"fullNameI18nCode":[
""
],
"align":"left",
"isAmountChinese":false,
"__config__":{
"formId":"formItem25e841",
"yunzhupaasKey":"inputNumber",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"预计金额",
"trigger":[
"blur",
"change"
],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576045553,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-number",
"tag":"YunzhupaasInputNumber",
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"amount",
"__vModel__":"amount",
"disabled":false,
"id":"amount",
"placeholder":"请输入预计金额",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"resizable":true,
"max":9999999,
"fullName":"预计金额",
"label":"预计金额",
"sortable":false,
"thousands":false,
"addonAfter":"元",
"width":null,
"fixed":"none",
"style":{
"width":"100%"
},
"step":1,
"labelI18nCode":""
},
{
"yunzhupaasKey":"datePicker",
"clearable":true,
"resizable":true,
"format":"yyyy-MM-dd",
"fullName":"预计成交日期",
"fullNameI18nCode":[
""
],
"label":"预计成交日期",
"sortable":false,
"align":"left",
"__config__":{
"yunzhupaasKey":"datePicker",
"endRelationField":"",
"defaultValue":null,
"dragDisabled":false,
"className":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576099226,
"tagIcon":"icon-ym icon-ym-generator-date",
"startRelationField":"",
"defaultCurrent":false,
"tag":"YunzhupaasDatePicker",
"formId":"formItemc8fcbf",
"visibility":[
"pc",
"app"
],
"noShow":false,
"endTimeTarget":1,
"tipLabel":"",
"tableFixed":"none",
"startTimeType":1,
"endTimeRule":false,
"label":"预计成交日期",
"trigger":"change",
"startTimeRule":false,
"startTimeValue":null,
"endTimeValue":null,
"endTimeType":1,
"layout":"colFormItem",
"startTimeTarget":1,
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"close_date",
"width":null,
"__vModel__":"close_date",
"fixed":"none",
"style":{
"width":"100%"
},
"startTime":null,
"disabled":false,
"id":"close_date",
"placeholder":"请选择预计成交日期",
"endTime":null,
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"inputNumber",
"controls":false,
"precision":2,
"fullNameI18nCode":[
""
],
"align":"left",
"isAmountChinese":false,
"__config__":{
"formId":"formItem4b8dd6",
"yunzhupaasKey":"inputNumber",
"visibility":[
"pc",
"app"
],
"defaultValue":null,
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"赢单概率",
"trigger":[
"blur",
"change"
],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576216024,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-number",
"tag":"YunzhupaasInputNumber",
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"probability",
"__vModel__":"probability",
"disabled":false,
"id":"probability",
"placeholder":"请输入赢单概率",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"resizable":true,
"max":100,
"fullName":"赢单概率",
"label":"赢单概率",
"sortable":false,
"thousands":false,
"addonAfter":"%",
"width":null,
"fixed":"none",
"style":{
"width":"100%"
},
"step":1,
"labelI18nCode":""
},
{
"yunzhupaasKey":"datePicker",
"clearable":true,
"resizable":true,
"format":"yyyy-MM-dd",
"fullName":"最后跟进日期",
"fullNameI18nCode":[
""
],
"label":"最后跟进日期",
"sortable":false,
"align":"left",
"__config__":{
"yunzhupaasKey":"datePicker",
"endRelationField":"",
"defaultValue":null,
"dragDisabled":false,
"className":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576256284,
"tagIcon":"icon-ym icon-ym-generator-date",
"startRelationField":"",
"defaultCurrent":true,
"tag":"YunzhupaasDatePicker",
"formId":"formIteme2d97f",
"visibility":[
"pc",
"app"
],
"noShow":false,
"endTimeTarget":1,
"tipLabel":"",
"tableFixed":"none",
"startTimeType":1,
"endTimeRule":false,
"label":"最后跟进日期",
"trigger":"change",
"startTimeRule":false,
"startTimeValue":null,
"endTimeValue":null,
"endTimeType":1,
"layout":"colFormItem",
"startTimeTarget":1,
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"last_followup_date",
"width":null,
"__vModel__":"last_followup_date",
"fixed":"none",
"style":{
"width":"100%"
},
"startTime":null,
"disabled":true,
"id":"last_followup_date",
"placeholder":"请选择最后跟进日期",
"endTime":null,
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
}
]
export default columnList

View File

@@ -0,0 +1,205 @@
const searchList = [
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"showCount":false,
"__config__":{
"formId":"formItemd62246",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机名称",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575491289,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":12
},
"readonly":false,
"prop":"opportunity_name",
"__vModel__":"opportunity_name",
"searchMultiple":false,
"disabled":false,
"id":"opportunity_name",
"placeholder":"请输入商机名称",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"searchType":2,
"maxlength":50,
"fullName":"商机名称",
"label":"商机名称",
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"isKeyword":true,
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"searchType":1,
"multiple":false,
"fullName":"商机阶段",
"fullNameI18nCode":[
""
],
"label":"商机阶段",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576009744,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem97b95a",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797444616478523397",
"tipLabel":"",
"tableFixed":"none",
"label":"商机阶段",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"opportunity_stage",
"options":[],
"__vModel__":"opportunity_stage",
"searchMultiple":true,
"isKeyword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"opportunity_stage",
"placeholder":"请选择商机阶段",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"datePicker",
"clearable":true,
"searchType":3,
"format":"yyyy-MM-dd",
"fullName":"预计成交日期",
"fullNameI18nCode":[
""
],
"label":"预计成交日期",
"__config__":{
"yunzhupaasKey":"datePicker",
"endRelationField":"",
"defaultValue":null,
"dragDisabled":false,
"className":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576099226,
"tagIcon":"icon-ym icon-ym-generator-date",
"startRelationField":"",
"defaultCurrent":false,
"tag":"YunzhupaasDatePicker",
"formId":"formItemc8fcbf",
"visibility":[
"pc",
"app"
],
"noShow":false,
"endTimeTarget":1,
"tipLabel":"",
"tableFixed":"none",
"startTimeType":1,
"endTimeRule":false,
"label":"预计成交日期",
"trigger":"change",
"startTimeRule":false,
"startTimeValue":null,
"endTimeValue":null,
"endTimeType":1,
"layout":"colFormItem",
"startTimeTarget":1,
"regList":[],
"tableAlign":"left",
"span":12
},
"prop":"close_date",
"__vModel__":"close_date",
"searchMultiple":false,
"isKeyword":false,
"style":{
"width":"100%"
},
"startTime":null,
"disabled":false,
"id":"close_date",
"placeholder":"请选择预计成交日期",
"endTime":null,
"value":[],
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
}
]
export default searchList

View File

@@ -0,0 +1,738 @@
const superQueryJson = [
{
"clearable":true,
"maxlength":50,
"useScan":false,
"suffixIcon":"",
"fullName":"商机编码",
"fullNameI18nCode":[
""
],
"addonAfter":"",
"showCount":false,
"__config__":{
"formId":"formItemccf93a",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机编码",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575485596,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":12
},
"readonly":false,
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"__vModel__":"opportunity_code",
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"opportunity_code",
"placeholder":"请输入商机编码",
"prefixIcon":"",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":50,
"useScan":false,
"suffixIcon":"",
"fullName":"商机名称",
"fullNameI18nCode":[
""
],
"addonAfter":"",
"showCount":false,
"__config__":{
"formId":"formItemd62246",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机名称",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575491289,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":12
},
"readonly":false,
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"__vModel__":"opportunity_name",
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"opportunity_name",
"placeholder":"请输入商机名称",
"prefixIcon":"",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"popupType":"drawer",
"hasPage":false,
"filterable":false,
"clearable":true,
"modelId":"806858527036409349",
"fullName":"客户",
"pageSize":20,
"columnOptions":[
{
"label":"企业名称",
"value":"company_name"
},
{
"label":"类型",
"value":"entity_type"
},
{
"label":"归属组织",
"value":"org_id"
},
{
"label":"客户负责人",
"value":"owner_id"
},
{
"label":"客户等级",
"value":"customer_level"
}
],
"fullNameI18nCode":[
""
],
"relationField":"company_name",
"queryType":0,
"__config__":{
"formId":"formItemd16307",
"yunzhupaasKey":"relationForm",
"visibility":[
"pc",
"app"
],
"defaultValue":"",
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"客户",
"trigger":"change",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575610653,
"layout":"colFormItem",
"transferList":[],
"tagIcon":"icon-ym icon-ym-generator-menu",
"tag":"YunzhupaasRelationForm",
"regList":[],
"tableAlign":"left",
"span":24
},
"extraOptions":[],
"popupTitle":"选择客户",
"__vModel__":"customer_id",
"style":{
"width":"100%"
},
"disabled":false,
"id":"customer_id",
"placeholder":"请选择客户",
"popupWidth":"70%",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"propsValue":"company_id"
},
{
"clearable":true,
"ableIds":[],
"multiple":false,
"fullName":"商机负责人",
"fullNameI18nCode":[
""
],
"__config__":{
"formId":"formItem133e27",
"yunzhupaasKey":"usersSelect",
"visibility":[
"pc",
"app"
],
"defaultValue":"",
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机负责人",
"trigger":"change",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774575984909,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-founder",
"defaultCurrent":true,
"tag":"YunzhupaasUsersSelect",
"regList":[],
"tableAlign":"left",
"span":12
},
"__vModel__":"owner_id",
"style":{
"width":"100%"
},
"selectType":"all",
"disabled":false,
"id":"owner_id",
"placeholder":"请选择商机负责人",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"filterable":false,
"clearable":true,
"multiple":false,
"fullName":"商机阶段",
"fullNameI18nCode":[
""
],
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576009744,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem97b95a",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797444616478523397",
"tipLabel":"",
"tableFixed":"none",
"label":"商机阶段",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":12
},
"options":[],
"__vModel__":"opportunity_stage",
"style":{
"width":"100%"
},
"disabled":false,
"id":"opportunity_stage",
"placeholder":"请选择商机阶段",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"controls":false,
"max":9999999,
"precision":2,
"fullName":"预计金额",
"fullNameI18nCode":[
""
],
"thousands":false,
"isAmountChinese":false,
"addonAfter":"元",
"__config__":{
"formId":"formItem25e841",
"yunzhupaasKey":"inputNumber",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"预计金额",
"trigger":[
"blur",
"change"
],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576045553,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-number",
"tag":"YunzhupaasInputNumber",
"regList":[],
"tableAlign":"left",
"span":12
},
"__vModel__":"amount",
"style":{
"width":"100%"
},
"step":1,
"disabled":false,
"id":"amount",
"placeholder":"请输入预计金额",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"format":"yyyy-MM-dd",
"fullName":"预计成交日期",
"fullNameI18nCode":[
""
],
"__config__":{
"yunzhupaasKey":"datePicker",
"endRelationField":"",
"defaultValue":null,
"dragDisabled":false,
"className":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576099226,
"tagIcon":"icon-ym icon-ym-generator-date",
"startRelationField":"",
"defaultCurrent":false,
"tag":"YunzhupaasDatePicker",
"formId":"formItemc8fcbf",
"visibility":[
"pc",
"app"
],
"noShow":false,
"endTimeTarget":1,
"tipLabel":"",
"tableFixed":"none",
"startTimeType":1,
"endTimeRule":false,
"label":"预计成交日期",
"trigger":"change",
"startTimeRule":false,
"startTimeValue":null,
"endTimeValue":null,
"endTimeType":1,
"layout":"colFormItem",
"startTimeTarget":1,
"regList":[],
"tableAlign":"left",
"span":12
},
"__vModel__":"close_date",
"style":{
"width":"100%"
},
"startTime":null,
"disabled":false,
"id":"close_date",
"placeholder":"请选择预计成交日期",
"endTime":null,
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":200,
"fullName":"商机简介",
"fullNameI18nCode":[
""
],
"autoSize":{
"minRows":3,
"maxRows":3
},
"showCount":false,
"__config__":{
"formId":"formItemdff872",
"yunzhupaasKey":"textarea",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"商机简介",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576122837,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-textarea",
"tag":"YunzhupaasTextarea",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"__vModel__":"desciption",
"style":{
"width":"100%"
},
"disabled":false,
"id":"desciption",
"placeholder":"请输入商机简介",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"controls":false,
"max":100,
"precision":2,
"fullName":"赢单概率",
"fullNameI18nCode":[
""
],
"thousands":false,
"isAmountChinese":false,
"addonAfter":"%",
"__config__":{
"formId":"formItem4b8dd6",
"yunzhupaasKey":"inputNumber",
"visibility":[
"pc",
"app"
],
"defaultValue":null,
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"赢单概率",
"trigger":[
"blur",
"change"
],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576216024,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-number",
"tag":"YunzhupaasInputNumber",
"regList":[],
"tableAlign":"left",
"span":12
},
"__vModel__":"probability",
"style":{
"width":"100%"
},
"step":1,
"disabled":false,
"id":"probability",
"placeholder":"请输入赢单概率",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"format":"yyyy-MM-dd",
"fullName":"最后跟进日期",
"fullNameI18nCode":[
""
],
"__config__":{
"yunzhupaasKey":"datePicker",
"endRelationField":"",
"defaultValue":null,
"dragDisabled":false,
"className":[],
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576256284,
"tagIcon":"icon-ym icon-ym-generator-date",
"startRelationField":"",
"defaultCurrent":true,
"tag":"YunzhupaasDatePicker",
"formId":"formIteme2d97f",
"visibility":[
"pc",
"app"
],
"noShow":false,
"endTimeTarget":1,
"tipLabel":"",
"tableFixed":"none",
"startTimeType":1,
"endTimeRule":false,
"label":"最后跟进日期",
"trigger":"change",
"startTimeRule":false,
"startTimeValue":null,
"endTimeValue":null,
"endTimeType":1,
"layout":"colFormItem",
"startTimeTarget":1,
"regList":[],
"tableAlign":"left",
"span":12
},
"__vModel__":"last_followup_date",
"style":{
"width":"100%"
},
"startTime":null,
"disabled":true,
"id":"last_followup_date",
"placeholder":"请选择最后跟进日期",
"endTime":null,
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":200,
"fullName":"竞争对手信息",
"fullNameI18nCode":[
""
],
"autoSize":{
"minRows":3,
"maxRows":3
},
"showCount":false,
"__config__":{
"formId":"formItemf00947",
"yunzhupaasKey":"textarea",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"竞争对手信息",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576286031,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-textarea",
"tag":"YunzhupaasTextarea",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"__vModel__":"competitor",
"style":{
"width":"100%"
},
"disabled":false,
"id":"competitor",
"placeholder":"请输入竞争对手信息",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":200,
"fullName":"决策分析",
"fullNameI18nCode":[
""
],
"autoSize":{
"minRows":3,
"maxRows":3
},
"showCount":false,
"__config__":{
"formId":"formItemd031ca",
"yunzhupaasKey":"textarea",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"决策分析",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576313718,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-textarea",
"tag":"YunzhupaasTextarea",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"__vModel__":"analysis",
"style":{
"width":"100%"
},
"disabled":false,
"id":"analysis",
"placeholder":"请输入决策分析",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"clearable":true,
"maxlength":200,
"fullName":"备注",
"fullNameI18nCode":[
""
],
"autoSize":{
"minRows":3,
"maxRows":3
},
"showCount":false,
"__config__":{
"formId":"formItem327c50",
"yunzhupaasKey":"textarea",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"备注",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"crm_opportunity",
"renderKey":1774576314248,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-textarea",
"tag":"YunzhupaasTextarea",
"regList":[],
"tableAlign":"left",
"span":24
},
"readonly":false,
"__vModel__":"remark",
"style":{
"width":"100%"
},
"disabled":false,
"id":"remark",
"placeholder":"请输入备注",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
}
]
export default superQueryJson

View File

@@ -0,0 +1,652 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box" v-if="getSearchList.length">
<BasicForm @register="registerSearchForm" :schemas="getSearchList"
@advanced-change="redoHeight" @submit="handleSearchSubmit" @reset="handleSearchReset"
class="search-form">
</BasicForm>
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<BasicTable @register="registerTable" v-bind="getTableBindValue" ref="tableRef"
@columns-change="handleColumnChange">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add"
@click="addHandle()"> {{t('common.add2Text','新增')}}</a-button>
</template>
<template #toolbar>
<a-tooltip placement="top">
<template #title>
<span>{{ t('common.superQuery') }}</span>
</template>
<filter-outlined @click="openSuperQuery(true, { columnOptions: superQueryJson })" />
</a-tooltip>
</template>
<template #toolbarAfter>
<ViewList :menuId="route.meta.modelId" :viewList="viewList" @itemClick="handleViewClick" @reload="initViewList" />
<ViewSetting :menuId="route.meta.modelId" :viewList="viewList" :currentView="currentView" @reload="initViewList" />
</template>
<template #bodyCell="{ column, record, index }">
<template v-for="(item, index) in childColumnList" v-if="childColumnList.length">
<template
v-if="column?.id?.includes('-') && item.children && item.children[0] && column.key === item.children[0]?.dataIndex">
<ChildTableColumn :data="record[item.prop]" :head="item.children"
@toggleExpand="toggleExpand(record, item.prop+`Expand`)" @toDetail="toDetail"
:expand="record[item.prop+`Expand`]" :key="index" :showOverflow="true "/>
</template>
</template>
<template v-if="!(record.top || column.id?.includes('-'))">
<template v-if="column.yunzhupaasKey === 'relationForm'">
<p class="link-text"
@click="toDetail(column.modelId, record[column.dataIndex+`_id`], column.propsValue)">
{{ record[column.dataIndex] }}</p>
</template>
<template v-if="column.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="record[column.prop]" :precision="column.precision" :thousands="column.thousands" disabled detailed />
</template>
<template v-if="column.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="record[column.prop]"
:isStorage="column.isStorage"
:precision="column.precision"
:thousands="column.thousands"
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.prop]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.prop]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.prop]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="true"
detailed />
</template>
</template>
<template v-if="column.key === 'action' && !record.top">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form ref="formRef" @reload="reload" />
<Detail ref="detailRef" />
<ExportModal @register="registerExportModal" @download="handleDownload" />
<ImportModal @register="registerImportModal" @reload="reload" />
<PrintSelect @register="registerPrintSelect" @change="handleShowBrowse" />
<PrintBrowse @register="registerPrintBrowse" />
<RelationDetail ref="relationDetailRef" />
<SuperQueryModal @register="registerSuperQueryModal" @superQuery="handleSuperQuery" />
</div>
</template>
<script lang="ts" setup>
import { getList, del, exportData, batchDelete } from './helper/api';
import { getConfigData,getViewList } from '@/api/onlineDev/visualDev';
import { getDictionaryDataSelector } from '@/api/systemData/dictionary';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { getOrgByOrganizeCondition,getDepartmentSelectAsyncList } from '@/api/permission/organize';
import { ref, reactive, onMounted, toRefs, computed, unref, nextTick, toRaw, provide } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useOrganizeStore } from '@/store/modules/organize';
import { useUserStore } from '@/store/modules/user';
import { BasicModal, useModal } from '@/components/Modal';
import { usePopup } from '@/components/Popup';
import { ScrollContainer } from '@/components/Container';
import { BasicLeftTree, TreeActionType } from '@/components/Tree';
import { BasicForm, useForm } from '@/components/Form';
import { BasicTable, useTable, TableAction, ActionItem, TableActionType, SorterResult } from '@/components/Table';
import { SuperQueryModal } from '@/components/CommonModal';
import Form from './Form.vue';
import Detail from './Detail.vue';
// 有关联表单详情:开始
import RelationDetail from '@/views/common/dynamicModel/list/detail/index.vue';
// 有关联表单详情:结束
import ChildTableColumn from '@/views/common/dynamicModel/list/ChildTableColumn.vue';
import { ExportModal } from '@/components/CommonModal';
import { downloadByUrl } from '@/utils/file/download';
import { ImportModal} from '@/components/CommonModal';
// 打印模板多条生成PrintSelect
import PrintSelect from '@/components/PrintDesign/printSelect/index.vue';
import PrintBrowse from '@/components/PrintDesign/printBrowse/index.vue';
import { useRoute,useRouter } from 'vue-router';
import { FilterOutlined } from '@ant-design/icons-vue';
import { getSearchFormSchemas } from '@/components/FormGenerator/src/helper/transform';
import { cloneDeep } from 'lodash-es';
import columnList from './helper/columnList';
import searchList from './helper/searchList';
import superQueryJson from './helper/superQueryJson';
import { dyOptionsList, systemComponentsList } from '@/components/FormGenerator/src/helper/config';
import { thousandsFormat, getParamList} from '@/utils/yunzhupaas';
import { usePermission } from '@/hooks/web/usePermission';
import ViewSetting from '@/views/common/dynamicModel/list/components/ViewSetting.vue';
import ViewList from '@/views/common/dynamicModel/list/components/ViewList.vue';
interface State {
config: any;
columnList: any[];
printListOptions: any[];
columnBtnsList: any[];
customBtnsList: any[];
treeFieldNames: any;
leftTreeData: any[];
leftTreeLoading: boolean;
treeActiveId: string;
treeActiveNodePath: any;
columns: any[];
complexColumns: any[];
childColumnList: any[];
exportList: any[];
cacheList: any[];
currFlow: any;
isCustomCopy: boolean;
candidateType: number;
currRow: any;
workFlowFormData: any;
expandObj: any;
columnSettingList: any[];
searchSchemas: any[];
treeRelationObj: any;
treeQueryJson: any;
leftTreeActiveInfo: any;
keyword: string;
viewList: any[];
currentView: any;
}
const route = useRoute();
const { hasBtnP } = usePermission();
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const organizeStore = useOrganizeStore();
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const [registerExportModal, { openModal: openExportModal, closeModal: closeExportModal, setModalProps: setExportModalProps }] = useModal();
const [registerImportModal, { openModal: openImportModal }] = useModal();
const [registerSuperQueryModal, { openModal: openSuperQuery }] = useModal();
const formRef = ref<any>(null);
const tableRef = ref<Nullable<TableActionType>>(null);
const detailRef = ref<any>(null);
const relationDetailRef = ref<any>(null);
const state = reactive<State>({
config: {},
columnList: [],
printListOptions: [],
columnBtnsList: [],
customBtnsList: [],
treeFieldNames: {
children: 'children' ,
title: 'fullName' ,
key: 'id' ,
isLeaf: 'isLeaf',
},
leftTreeData: [],
leftTreeLoading: false,
treeActiveId: '',
treeActiveNodePath: [],
columns: [],
complexColumns: [], // 复杂表头
childColumnList: [],
exportList: [],
cacheList: [],
currFlow: {},
isCustomCopy: false,
candidateType: 1,
currRow: {},
workFlowFormData: {},
expandObj: {},
columnSettingList: [],
searchSchemas: [],
treeRelationObj: null,
treeQueryJson: {},
leftTreeActiveInfo: {},
keyword: '',
viewList: [],
currentView: {},
});
const defaultSearchInfo = {
menuId: route.meta.modelId as string,
moduleId:'807147983731689285',
superQueryJson: '',
dataType:0,
};
const searchInfo = reactive({
...cloneDeep(defaultSearchInfo),
});
const { childColumnList, searchSchemas, viewList, currentView} = toRefs(state);
const [registerSearchForm, { updateSchema, resetFields, submit: searchFormSubmit, setFieldsValue}] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
});
const [registerTable, { reload, setLoading, getFetchParams, getSelectRows, getSelectRowKeys, redoHeight,clearSelectedRowKeys }] = useTable({
api: getList,
immediate: false,
clickToRowSelect: false,
tableSetting: { setting: false },
afterFetch: (data) => {
const list = data.map((o) => ({
...o,
...state.expandObj,
}));
state.cacheList = cloneDeep(list);
return list;
},
});
const [registerChildTable] = useTable({
pagination: false,
canResize: false,
showTableSetting: false,
});
provide('getLeftTreeActiveInfo', () => state.leftTreeActiveInfo);
const getHasBatchBtn = computed(() => {
let btnsList =[]
return !!btnsList.length
});
const getColumns = computed(() => {
const columns = state.complexColumns;
return setListValue(state.currentView?.columnList, columns, 'prop');
});
const getSearchList = computed(() => {
const searchSchemas = cloneDeep(state.searchSchemas).map(o => ({ ...o, show: true }));
return setListValue(state.currentView?.searchList, searchSchemas, 'field');
});
const getTableBindValue = computed(() => {
let columns = unref(getColumns);
const defaultSortConfig= [];
const sortField = defaultSortConfig.map(o => (o.sort === 'desc' ? '-' : '') + o.field);
const data: any = {
pagination: { pageSize: 20 }, //有分页
searchInfo: unref(searchInfo),
defSort: { sidx: sortField.join(',') },
sortFn: (sortInfo: SorterResult | SorterResult[]) => {
if (Array.isArray(sortInfo)) {
const sortList = sortInfo.map(o => (o.order === 'descend' ? '-' : '') + o.field);
return { sidx: sortList.join(',') };
} else {
const { field, order } = sortInfo;
if (field && order) {
// 排序字段
return { sidx: (order === 'descend' ? '-' : '') + field };
} else {
return {};
}
}
},
ellipsis:true ,
columns,
bordered: true,
actionColumn: {
width: 150,
title: t('component.table.action'),
dataIndex: 'action',
},
};
if (unref(getHasBatchBtn)) {
const rowSelection: any = { type: 'checkbox' };
data.rowSelection = rowSelection;
}
return data;
});
function init() {
state.config = {};
searchInfo.menuId = route.meta.modelId as string;
state.columnList = columnList;
setLoading(true);
getSearchSchemas();
getColumnList();
initViewList();
nextTick(() => {
unref(getSearchList)?.length ? searchFormSubmit() : reload({ page: 1 });
});
}
function getSearchSchemas() {
const schemas = getSearchFormSchemas(searchList);
state.searchSchemas = schemas;
schemas.forEach((cur) => {
const config = cur.__config__;
if (dyOptionsList.includes(config.yunzhupaasKey)) {
if (config.dataType === 'dictionary') {
if (!config.dictionaryType) return;
getDictionaryDataSelector(config.dictionaryType).then((res) => {
updateSchema([{ field: cur.field, componentProps: { options: res.data.list } }]);
});
}
if (config.dataType === 'dynamic') {
if (!config.propsUrl) return;
const query = { paramList: getParamList(config.templateJson) };
getDataInterfaceRes(config.propsUrl, query).then((res) => {
const data = Array.isArray(res.data) ? res.data : [];
updateSchema([{ field: cur.field, componentProps: { options: data } }]);
});
}
}
cur.defaultValue = cur.value;
});
}
function getColumnList() {
// 没有开启列表权限
let columnList = state.columnList;
state.exportList = columnList;
let columns = columnList.map((o) => ({
...o,
title: o.labelI18nCode ? t(o.labelI18nCode, o.label) : o.label,
dataIndex: o.prop,
align: o.align,
fixed: o.fixed == 'none' ? false : o.fixed,
sorter: o.sortable ? { multiple: 1 } : o.sortable,
width: o.width || 100,
}));
//添加复杂表头
columns = getComplexColumns(columns);
state.columns = columns.filter((o) => o.prop.indexOf('-') < 0);
//子表表头
getChildComplexColumns(columns);
}
//复杂表头
function getComplexColumns(columns) {
//这里生成复杂表头的配置
let complexHeaderList: any[] = [];
if (!complexHeaderList.length) return columns;
let childColumns: any[] = [];
let firstChildColumns: string[] = [];
for (let i = 0; i < complexHeaderList.length; i++) {
const e = complexHeaderList[i];
e.label = e.fullName;
e.labelI18nCode = e.fullNameI18nCode;
e.title = e.fullNameI18nCode ? t(e.fullNameI18nCode, e.fullName) : e.fullName;
e.align = e.align;
e.dataIndex = e.id;
e.prop = e.id;
e.children = [];
e.yunzhupaasKey = 'complexHeader';
if (e.childColumns?.length) {
childColumns.push(...e.childColumns);
for (let k = 0; k < e.childColumns.length; k++) {
const item = e.childColumns[k];
for (let j = 0; j < columns.length; j++) {
const o = columns[j];
if (o.prop == item && o.fixed !== 'left' && o.fixed !== 'right') e.children.push({ ...o });
}
}
}
if (e.children.length) firstChildColumns.push(e.children[0].prop);
}
complexHeaderList = complexHeaderList.filter(o => o.children.length);
let list: any[] = [];
for (let i = 0; i < columns.length; i++) {
const e = columns[i];
if (!childColumns.includes(e.prop)) {
list.push(e);
} else {
if (firstChildColumns.includes(e.prop)) {
const item = complexHeaderList.find(o => o.childColumns.includes(e.prop));
list.push(item);
}
}
}
return list;
}
//子表表头
function getChildComplexColumns(columnList) {
let list: any[] = [];
for (let i = 0; i < columnList.length; i++) {
const e = columnList[i];
if (!e.prop.includes('-')) {
list.push(e);
} else {
let prop = e.prop.split('-')[0];
let vModel = e.prop.split('-')[1];
let label = e.label.split('-')[0];
let childLabel = e.label.replace(label + '-', '');
if (e.fullNameI18nCode && Array.isArray(e.fullNameI18nCode) && e.fullNameI18nCode[0]) label = t(e.fullNameI18nCode[0], label);
let newItem = {
align: 'center',
yunzhupaasKey: 'table',
prop,
label,
title: label,
dataIndex: prop,
children: [],
};
e.dataIndex = vModel;
e.title = e.labelI18nCode ? t(e.labelI18nCode, childLabel) : childLabel;
if (!state.expandObj.hasOwnProperty(prop+`Expand`)) state.expandObj[prop+`Expand`] = false;
if (!list.some((o) => o.prop === prop)) list.push(newItem);
for (let i = 0; i < list.length; i++) {
if (list[i].prop === prop) {
list[i].children.push(e);
break;
}
}
}
}
// 行内分组展示
getMergeList(list);
state.complexColumns = list;
state.childColumnList = list.filter((o) => o.yunzhupaasKey === 'table');
// 子表分组展示宽度取100
for (let i = 0; i < state.childColumnList.length; i++) {
const e = state.childColumnList[i];
if (e.children?.length) e.children = e.children.map(o => ({ ...o, width: 100 }));
}
}
function getMergeList(list) {
list.forEach((item) => {
if (item.yunzhupaasKey === 'table' && item.children && item.children.length) {
item.children.forEach((child, index) => {
if (index == 0) {
child.customCell = () => ({
rowspan: 1,
colspan: item.children.length,
class: 'child-table-box',
});
} else {
child.customCell = () => ({
rowspan: 0,
colspan: 0,
});
}
});
}
});
}
function toggleExpand(row, field) {
row[field] = !row[field];
}
// 关联表单查看详情
function toDetail(modelId, id, propsValue) {
if (!id) return;
getConfigData(modelId).then((res) => {
if (!res.data || !res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = 'general';
formConf.hasPrintBtn = false;
formConf.customBtns = [];
const data = { id, formConf, modelId, propsValue};
relationDetailRef.value?.init(data);
});
}
function handleColumnChange(data) {
state.columnSettingList = data;
}
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText','编辑') ,
onClick: updateHandle.bind(null, record),
},
{
label: t('common.delText','删除') ,
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
{
label: t('common.detailText','详情') ,
onClick: goDetail.bind(null, record),
},
];
}
// 编辑
function updateHandle(record) {
// 不带工作流
const data = {
id: record.id,
menuId: searchInfo.menuId,
allList: state.cacheList,
};
formRef.value?.init(data);
}
// 删除
function handleDelete(id) {
const query={ids:[id] }
batchDelete(query).then((res) => {
createMessage.success(res.msg);
clearSelectedRowKeys();
reload();
});
}
// 查看详情
function goDetail(record) {
// 不带流程
const data = {
id: record.id,
};
detailRef.value?.init(data);
}
// 新增
function addHandle() {
// 不带流程新增
const data = {
id: '',
menuId: searchInfo.menuId,
allList: state.cacheList,
};
formRef.value?.init(data);
}
// 高级查询
function handleSuperQuery(superQueryJson) {
searchInfo.superQueryJson = superQueryJson;
reload({ page: 1 });
}
function handleSearchReset() {
clearSelectedRowKeys();
if (!state.resetFromTree) updateSearchFormValue();
if (state.resetFromTree) state.resetFromTree = false;
}
function handleSearchSubmit(data) {
clearSelectedRowKeys();
let obj = {
...defaultSearchInfo,
superQueryJson: searchInfo.superQueryJson,
...data,
};
Object.keys(searchInfo).map(key => {
delete searchInfo[key];
});
for (let [key, value] of Object.entries(obj)) {
searchInfo[key.replaceAll('-', '_')] = value;
}
console.log(searchInfo);
reload({ page: 1 });
}
function updateSearchFormValue() {
if (!state.treeActiveId) return searchFormSubmit();
let queryJson: any = {};
let leftTreeActiveInfo: any = {};
const isMultiple = !state.treeRelationObj ? false : state.treeRelationObj.searchMultiple;
//多级左侧树,需要拼父级->转为查询参数
if (state.treeRelationObj && state.treeRelationObj.yunzhupaasKey && ['organizeSelect', 'cascader', 'areaSelect'].includes(state.treeRelationObj.yunzhupaasKey)) {
let currValue = [];
currValue = state.treeActiveNodePath.map(o => o[state.treeFieldNames.key]);
queryJson = { '': isMultiple ? [currValue] : currValue };
leftTreeActiveInfo = { '': state.treeRelationObj.multiple ? [currValue] : currValue };
} else {
queryJson = { '': isMultiple ? [state.treeActiveId] : state.treeActiveId };
leftTreeActiveInfo = { '': state.treeRelationObj.multiple ? [state.treeActiveId] : state.treeActiveId };
}
state.leftTreeActiveInfo = leftTreeActiveInfo;
if(unref(getSearchList)?.length){
// 有搜索列表
setFieldsValue(queryJson);
searchFormSubmit();
}else{
// 无搜索列表
handleSearchSubmit(queryJson);
}
}
function initViewList(currentId = '') {
const query = {
menuId: route.meta.modelId,
};
getViewList(query).then(res => {
const columns : any[]= state.complexColumns;
const searchList: any[] = state.searchSchemas.map(o => ({ label: o.label, id: o.field, show: o.show, labelI18nCode: o.labelI18nCode }));
const columnList: any[] = columns.map(o => ({ label: o.label, id: o.prop, show: true, fixed: o.fixed || 'none', labelI18nCode: o.labelI18nCode }));
state.viewList = (res.data || []).map(o => {
if (o.type == 0) return { ...o, searchList, columnList };
return { ...o, searchList: o.searchList ? JSON.parse(o.searchList) : [], columnList: o.columnList ? JSON.parse(o.columnList) : [] };
});
if (currentId) {
state.currentView = state.viewList.filter(o => o.id === currentId)[0] || state.viewList[0];
} else {
state.currentView = state.viewList.filter(o => o.status === 1)[0] || state.viewList[0];
}
});
}
function handleViewClick(item) {
state.currentView = item;
}
function setListValue(data: any[] = [], defaultData: any[] = [], key) {
let list: any[] = [];
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < defaultData.length; j++) {
if (data[i].show && data[i].id == defaultData[j][key]) list.push(defaultData[j]);
}
}
return list;
}
onMounted(() => {
init();
});
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
import { defHttp } from '@/utils/http/axios';
// 获取列表
export function getList(data) {
return defHttp.post({ url: '/api/bcm/MdmCompany/getList', data });
}
// 新建
export function create(data) {
return defHttp.post({ url:'/api/bcm/MdmCompany', data });
}
// 修改
export function update(data) {
return defHttp.put({ url: '/api/bcm/MdmCompany/'+ data.id, data });
}
// 详情(无转换数据)
export function getInfo(id) {
return defHttp.get({ url: '/api/bcm/MdmCompany/' + id });
}
// 获取(转换数据)
export function getDetailInfo(id) {
return defHttp.get({ url: '/api/bcm/MdmCompany/detail/' + id });
}
// 删除
export function del(id) {
return defHttp.delete({ url: '/api/bcm/MdmCompany/' + id });
}
// 批量删除数据
export function batchDelete(data) {
return defHttp.delete({ url: '/api/bcm/MdmCompany/batchRemove', data });
}
// 导出
export function exportData(data) {
return defHttp.post({ url: '/api/bcm/MdmCompany/Actions/Export', data });
}

View File

@@ -0,0 +1,784 @@
const columnList = [
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItem7a8e2d",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"企业编码",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774517618858,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":8
},
"readonly":false,
"prop":"company_code",
"__vModel__":"company_code",
"disabled":false,
"id":"company_code",
"placeholder":"请输入企业编码",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"企业编码",
"label":"企业编码",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItem977ea9",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"企业名称",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"mdm_company",
"renderKey":1774517624902,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"unique":true,
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":8
},
"readonly":false,
"prop":"company_name",
"__vModel__":"company_name",
"disabled":false,
"id":"company_name",
"placeholder":"请输入企业名称",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"企业名称",
"label":"企业名称",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItemdf17f1",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"简称/昵称",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774517625074,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":8
},
"readonly":false,
"prop":"short_name",
"__vModel__":"short_name",
"disabled":false,
"id":"short_name",
"placeholder":"请输入简称/昵称",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"简称/昵称",
"label":"简称/昵称",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"radio",
"resizable":true,
"buttonStyle":"solid",
"fullName":"类型",
"fullNameI18nCode":[
""
],
"label":"类型",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"id"
},
"optionType":"default",
"__config__":{
"yunzhupaasKey":"radio",
"defaultValue":"ORG",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":true,
"tableName":"mdm_company",
"renderKey":1774517712501,
"tagIcon":"icon-ym icon-ym-generator-radio",
"tag":"YunzhupaasRadio",
"formId":"formItem60d037",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"static",
"dictionaryType":"",
"tipLabel":"",
"tableFixed":"none",
"label":"类型",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":8
},
"size":"default",
"prop":"entity_type",
"width":null,
"options":[
{
"fullName":"企业",
"id":"ORG"
},
{
"fullName":"个人",
"id":"IND"
}
],
"__vModel__":"entity_type",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"entity_type",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"direction":"horizontal"
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItema17d51",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"社会信用代码",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"mdm_company",
"renderKey":1774518217493,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"unique":true,
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":8
},
"readonly":false,
"prop":"credit_code",
"__vModel__":"credit_code",
"disabled":false,
"id":"credit_code",
"placeholder":"请输入社会信用代码",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":50,
"fullName":"社会信用代码",
"label":"社会信用代码",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"企业范围",
"fullNameI18nCode":[
""
],
"label":"企业范围",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"id"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"EXT",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":true,
"tableName":"mdm_company",
"renderKey":1774518276461,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItemf9ae24",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"static",
"dictionaryType":"",
"tipLabel":"",
"tableFixed":"none",
"label":"企业范围",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":8
},
"prop":"company_scope",
"width":null,
"options":[
{
"fullName":"内部公司",
"id":"INT"
},
{
"fullName":"客商企业",
"id":"EXT"
}
],
"__vModel__":"company_scope",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"company_scope",
"placeholder":"请选择企业范围",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"行业代码",
"fullNameI18nCode":[
""
],
"label":"行业代码",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774518522631,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItemf04bd0",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"d59a3cf65f9847dbb08be449e3feae16",
"tipLabel":"",
"tableFixed":"none",
"label":"行业代码",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":8
},
"prop":"industry_code",
"width":null,
"options":[],
"__vModel__":"industry_code",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"industry_code",
"placeholder":"请选择行业代码",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"企业类型",
"fullNameI18nCode":[
""
],
"label":"企业类型",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774518361357,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem8deef9",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"9b542177a477488994ce09fff3c93901",
"tipLabel":"",
"tableFixed":"none",
"label":"企业类型",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":8
},
"prop":"enterprise_nature",
"width":null,
"options":[],
"__vModel__":"enterprise_nature",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"enterprise_nature",
"placeholder":"请选择企业类型",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"select",
"filterable":false,
"clearable":true,
"resizable":true,
"multiple":false,
"fullName":"企业规模",
"fullNameI18nCode":[
""
],
"label":"企业规模",
"sortable":false,
"align":"left",
"props":{
"label":"fullName",
"value":"enCode"
},
"__config__":{
"yunzhupaasKey":"select",
"defaultValue":"",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774518361164,
"tagIcon":"icon-ym icon-ym-generator-select",
"tag":"YunzhupaasSelect",
"formId":"formItem838852",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"dictionary",
"dictionaryType":"797424039713832965",
"tipLabel":"",
"tableFixed":"none",
"label":"企业规模",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":8
},
"prop":"enterprise_scale",
"width":null,
"options":[],
"__vModel__":"enterprise_scale",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"enterprise_scale",
"placeholder":"请选择企业规模",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"areaSelect",
"filterable":false,
"clearable":true,
"resizable":true,
"level":2,
"multiple":false,
"fullName":"所属地区",
"fullNameI18nCode":[
""
],
"label":"所属地区",
"sortable":false,
"align":"left",
"__config__":{
"formId":"formItema460b7",
"yunzhupaasKey":"areaSelect",
"visibility":[
"pc",
"app"
],
"defaultValue":[],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"所属地区",
"trigger":"change",
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774518666101,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-Provinces",
"tag":"YunzhupaasAreaSelect",
"regList":[],
"tableAlign":"left",
"span":8
},
"prop":"province_id",
"width":null,
"__vModel__":"province_id",
"fixed":"none",
"style":{
"width":"100%"
},
"disabled":false,
"id":"province_id",
"placeholder":"请选择所属地区",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
}
},
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"align":"left",
"showCount":false,
"__config__":{
"formId":"formItemed01bf",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"联系电话",
"trigger":"blur",
"showLabel":true,
"required":false,
"tableName":"mdm_company",
"renderKey":1774518656865,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"tag":"YunzhupaasInput",
"regList":[
{
"pattern":"/^1[3456789]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$/",
"message":"请输入正确的手机号码",
"messageI18nCode":"sys.validate.mobilePhone"
}
],
"tableAlign":"left",
"span":8
},
"readonly":false,
"prop":"phone",
"__vModel__":"phone",
"disabled":false,
"id":"phone",
"placeholder":"请输入联系电话",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"resizable":true,
"maxlength":11,
"fullName":"联系电话",
"label":"联系电话",
"sortable":false,
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"width":null,
"useMask":false,
"showPassword":false,
"fixed":"none",
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
}
]
export default columnList

View File

@@ -0,0 +1,149 @@
const searchList = [
{
"yunzhupaasKey":"input",
"useScan":false,
"suffixIcon":"",
"fullNameI18nCode":[
""
],
"showCount":false,
"__config__":{
"formId":"formItem977ea9",
"yunzhupaasKey":"input",
"visibility":[
"pc",
"app"
],
"noShow":false,
"tipLabel":"",
"tableFixed":"none",
"dragDisabled":false,
"className":[],
"label":"企业名称",
"trigger":"blur",
"showLabel":true,
"required":true,
"tableName":"mdm_company",
"renderKey":1774517624902,
"layout":"colFormItem",
"tagIcon":"icon-ym icon-ym-generator-input",
"unique":true,
"tag":"YunzhupaasInput",
"regList":[],
"tableAlign":"left",
"span":8
},
"readonly":false,
"prop":"company_name",
"__vModel__":"company_name",
"searchMultiple":false,
"disabled":false,
"id":"company_name",
"placeholder":"请输入企业名称",
"addonBefore":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}",
"blur":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"clearable":true,
"searchType":2,
"maxlength":50,
"fullName":"企业名称",
"label":"企业名称",
"addonAfter":"",
"maskConfig":{
"prefixType":1,
"useUnrealMask":false,
"maskType":1,
"unrealMaskLength":1,
"prefixLimit":0,
"suffixLimit":0,
"filler":"*",
"prefixSpecifyChar":"",
"suffixType":1,
"ignoreChar":"",
"suffixSpecifyChar":""
},
"isKeyword":true,
"useMask":false,
"showPassword":false,
"style":{
"width":"100%"
},
"prefixIcon":"",
"labelI18nCode":""
},
{
"yunzhupaasKey":"radio",
"searchType":1,
"buttonStyle":"solid",
"fullName":"类型",
"fullNameI18nCode":[
""
],
"label":"类型",
"props":{
"label":"fullName",
"value":"id"
},
"optionType":"default",
"__config__":{
"yunzhupaasKey":"radio",
"defaultValue":"ORG",
"dragDisabled":false,
"className":[],
"propsUrl":"",
"templateJson":[],
"showLabel":true,
"required":true,
"tableName":"mdm_company",
"renderKey":1774517712501,
"tagIcon":"icon-ym icon-ym-generator-radio",
"tag":"YunzhupaasRadio",
"formId":"formItem60d037",
"visibility":[
"pc",
"app"
],
"noShow":false,
"dataType":"static",
"dictionaryType":"",
"tipLabel":"",
"tableFixed":"none",
"label":"类型",
"trigger":"change",
"layout":"colFormItem",
"useCache":true,
"propsName":"",
"regList":[],
"tableAlign":"left",
"span":8
},
"size":"default",
"prop":"entity_type",
"options":[
{
"fullName":"企业",
"id":"ORG"
},
{
"fullName":"个人",
"id":"IND"
}
],
"__vModel__":"entity_type",
"searchMultiple":false,
"isKeyword":false,
"style":{
"width":"100%"
},
"disabled":false,
"id":"entity_type",
"labelI18nCode":"",
"on":{
"change":"({ data, rowIndex, formData, setFormData, setShowOrHide, setRequired, setDisabled, onlineUtils }) => {\n // 在此编写代码\n \n}"
},
"direction":"horizontal"
}
]
export default searchList

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,652 @@
<template>
<div class="yunzhupaas-content-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box" v-if="getSearchList.length">
<BasicForm @register="registerSearchForm" :schemas="getSearchList"
@advanced-change="redoHeight" @submit="handleSearchSubmit" @reset="handleSearchReset"
class="search-form">
</BasicForm>
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<BasicTable @register="registerTable" v-bind="getTableBindValue" ref="tableRef"
@columns-change="handleColumnChange">
<template #tableTitle>
<a-button type="primary" preIcon="icon-ym icon-ym-btn-add"
@click="addHandle()"> {{t('common.add2Text','新增')}}</a-button>
</template>
<template #toolbar>
<a-tooltip placement="top">
<template #title>
<span>{{ t('common.superQuery') }}</span>
</template>
<filter-outlined @click="openSuperQuery(true, { columnOptions: superQueryJson })" />
</a-tooltip>
</template>
<template #toolbarAfter>
<ViewList :menuId="route.meta.modelId" :viewList="viewList" @itemClick="handleViewClick" @reload="initViewList" />
<ViewSetting :menuId="route.meta.modelId" :viewList="viewList" :currentView="currentView" @reload="initViewList" />
</template>
<template #bodyCell="{ column, record, index }">
<template v-for="(item, index) in childColumnList" v-if="childColumnList.length">
<template
v-if="column?.id?.includes('-') && item.children && item.children[0] && column.key === item.children[0]?.dataIndex">
<ChildTableColumn :data="record[item.prop]" :head="item.children"
@toggleExpand="toggleExpand(record, item.prop+`Expand`)" @toDetail="toDetail"
:expand="record[item.prop+`Expand`]" :key="index" :showOverflow="true "/>
</template>
</template>
<template v-if="!(record.top || column.id?.includes('-'))">
<template v-if="column.yunzhupaasKey === 'relationForm'">
<p class="link-text"
@click="toDetail(column.modelId, record[column.dataIndex+`_id`], column.propsValue)">
{{ record[column.dataIndex] }}</p>
</template>
<template v-if="column.yunzhupaasKey === 'inputNumber'">
<yunzhupaas-input-number v-model:value="record[column.prop]" :precision="column.precision" :thousands="column.thousands" disabled detailed />
</template>
<template v-if="column.yunzhupaasKey === 'calculate'">
<yunzhupaas-calculate
v-model:value="record[column.prop]"
:isStorage="column.isStorage"
:precision="column.precision"
:thousands="column.thousands"
detailed />
</template>
<template v-if="column.yunzhupaasKey === 'sign'">
<yunzhupaas-sign v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'signature'">
<yunzhupaas-signature v-model:value="record[column.prop]" detailed />
</template>
<template v-if="column.yunzhupaasKey === 'rate'">
<yunzhupaas-rate v-model:value="record[column.prop]" :count="column.count" :allowHalf="column.allowHalf" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'slider'">
<yunzhupaas-slider v-model:value="record[column.prop]" :min="column.min" :max="column.max" :step="column.step" disabled />
</template>
<template v-if="column.yunzhupaasKey === 'uploadImg'">
<yunzhupaas-upload-img v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'uploadFile'">
<yunzhupaas-upload-file v-model:value="record[column.prop]" disabled detailed simple v-if="record[column.prop]?.length" />
</template>
<template v-if="column.yunzhupaasKey === 'input'">
<yunzhupaas-input
v-model:value="record[column.prop]"
:useMask="column.useMask"
:maskConfig="column.maskConfig"
:showOverflow="true"
detailed />
</template>
</template>
<template v-if="column.key === 'action' && !record.top">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Form ref="formRef" @reload="reload" />
<Detail ref="detailRef" />
<ExportModal @register="registerExportModal" @download="handleDownload" />
<ImportModal @register="registerImportModal" @reload="reload" />
<PrintSelect @register="registerPrintSelect" @change="handleShowBrowse" />
<PrintBrowse @register="registerPrintBrowse" />
<RelationDetail ref="relationDetailRef" />
<SuperQueryModal @register="registerSuperQueryModal" @superQuery="handleSuperQuery" />
</div>
</template>
<script lang="ts" setup>
import { getList, del, exportData, batchDelete } from './helper/api';
import { getConfigData,getViewList } from '@/api/onlineDev/visualDev';
import { getDictionaryDataSelector } from '@/api/systemData/dictionary';
import { getDataInterfaceRes } from '@/api/systemData/dataInterface';
import { getOrgByOrganizeCondition,getDepartmentSelectAsyncList } from '@/api/permission/organize';
import { ref, reactive, onMounted, toRefs, computed, unref, nextTick, toRaw, provide } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { useOrganizeStore } from '@/store/modules/organize';
import { useUserStore } from '@/store/modules/user';
import { BasicModal, useModal } from '@/components/Modal';
import { usePopup } from '@/components/Popup';
import { ScrollContainer } from '@/components/Container';
import { BasicLeftTree, TreeActionType } from '@/components/Tree';
import { BasicForm, useForm } from '@/components/Form';
import { BasicTable, useTable, TableAction, ActionItem, TableActionType, SorterResult } from '@/components/Table';
import { SuperQueryModal } from '@/components/CommonModal';
import Form from './Form.vue';
import Detail from './Detail.vue';
// 有关联表单详情:开始
import RelationDetail from '@/views/common/dynamicModel/list/detail/index.vue';
// 有关联表单详情:结束
import ChildTableColumn from '@/views/common/dynamicModel/list/ChildTableColumn.vue';
import { ExportModal } from '@/components/CommonModal';
import { downloadByUrl } from '@/utils/file/download';
import { ImportModal} from '@/components/CommonModal';
// 打印模板多条生成PrintSelect
import PrintSelect from '@/components/PrintDesign/printSelect/index.vue';
import PrintBrowse from '@/components/PrintDesign/printBrowse/index.vue';
import { useRoute,useRouter } from 'vue-router';
import { FilterOutlined } from '@ant-design/icons-vue';
import { getSearchFormSchemas } from '@/components/FormGenerator/src/helper/transform';
import { cloneDeep } from 'lodash-es';
import columnList from './helper/columnList';
import searchList from './helper/searchList';
import superQueryJson from './helper/superQueryJson';
import { dyOptionsList, systemComponentsList } from '@/components/FormGenerator/src/helper/config';
import { thousandsFormat, getParamList} from '@/utils/yunzhupaas';
import { usePermission } from '@/hooks/web/usePermission';
import ViewSetting from '@/views/common/dynamicModel/list/components/ViewSetting.vue';
import ViewList from '@/views/common/dynamicModel/list/components/ViewList.vue';
interface State {
config: any;
columnList: any[];
printListOptions: any[];
columnBtnsList: any[];
customBtnsList: any[];
treeFieldNames: any;
leftTreeData: any[];
leftTreeLoading: boolean;
treeActiveId: string;
treeActiveNodePath: any;
columns: any[];
complexColumns: any[];
childColumnList: any[];
exportList: any[];
cacheList: any[];
currFlow: any;
isCustomCopy: boolean;
candidateType: number;
currRow: any;
workFlowFormData: any;
expandObj: any;
columnSettingList: any[];
searchSchemas: any[];
treeRelationObj: any;
treeQueryJson: any;
leftTreeActiveInfo: any;
keyword: string;
viewList: any[];
currentView: any;
}
const route = useRoute();
const { hasBtnP } = usePermission();
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const organizeStore = useOrganizeStore();
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const [registerExportModal, { openModal: openExportModal, closeModal: closeExportModal, setModalProps: setExportModalProps }] = useModal();
const [registerImportModal, { openModal: openImportModal }] = useModal();
const [registerSuperQueryModal, { openModal: openSuperQuery }] = useModal();
const formRef = ref<any>(null);
const tableRef = ref<Nullable<TableActionType>>(null);
const detailRef = ref<any>(null);
const relationDetailRef = ref<any>(null);
const state = reactive<State>({
config: {},
columnList: [],
printListOptions: [],
columnBtnsList: [],
customBtnsList: [],
treeFieldNames: {
children: 'children' ,
title: 'fullName' ,
key: 'id' ,
isLeaf: 'isLeaf',
},
leftTreeData: [],
leftTreeLoading: false,
treeActiveId: '',
treeActiveNodePath: [],
columns: [],
complexColumns: [], // 复杂表头
childColumnList: [],
exportList: [],
cacheList: [],
currFlow: {},
isCustomCopy: false,
candidateType: 1,
currRow: {},
workFlowFormData: {},
expandObj: {},
columnSettingList: [],
searchSchemas: [],
treeRelationObj: null,
treeQueryJson: {},
leftTreeActiveInfo: {},
keyword: '',
viewList: [],
currentView: {},
});
const defaultSearchInfo = {
menuId: route.meta.modelId as string,
moduleId:'806858527036409349',
superQueryJson: '',
dataType:0,
};
const searchInfo = reactive({
...cloneDeep(defaultSearchInfo),
});
const { childColumnList, searchSchemas, viewList, currentView} = toRefs(state);
const [registerSearchForm, { updateSchema, resetFields, submit: searchFormSubmit, setFieldsValue}] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
});
const [registerTable, { reload, setLoading, getFetchParams, getSelectRows, getSelectRowKeys, redoHeight,clearSelectedRowKeys }] = useTable({
api: getList,
immediate: false,
clickToRowSelect: false,
tableSetting: { setting: false },
afterFetch: (data) => {
const list = data.map((o) => ({
...o,
...state.expandObj,
}));
state.cacheList = cloneDeep(list);
return list;
},
});
const [registerChildTable] = useTable({
pagination: false,
canResize: false,
showTableSetting: false,
});
provide('getLeftTreeActiveInfo', () => state.leftTreeActiveInfo);
const getHasBatchBtn = computed(() => {
let btnsList =[]
return !!btnsList.length
});
const getColumns = computed(() => {
const columns = state.complexColumns;
return setListValue(state.currentView?.columnList, columns, 'prop');
});
const getSearchList = computed(() => {
const searchSchemas = cloneDeep(state.searchSchemas).map(o => ({ ...o, show: true }));
return setListValue(state.currentView?.searchList, searchSchemas, 'field');
});
const getTableBindValue = computed(() => {
let columns = unref(getColumns);
const defaultSortConfig= [];
const sortField = defaultSortConfig.map(o => (o.sort === 'desc' ? '-' : '') + o.field);
const data: any = {
pagination: { pageSize: 20 }, //有分页
searchInfo: unref(searchInfo),
defSort: { sidx: sortField.join(',') },
sortFn: (sortInfo: SorterResult | SorterResult[]) => {
if (Array.isArray(sortInfo)) {
const sortList = sortInfo.map(o => (o.order === 'descend' ? '-' : '') + o.field);
return { sidx: sortList.join(',') };
} else {
const { field, order } = sortInfo;
if (field && order) {
// 排序字段
return { sidx: (order === 'descend' ? '-' : '') + field };
} else {
return {};
}
}
},
ellipsis:true ,
columns,
bordered: true,
actionColumn: {
width: 150,
title: t('component.table.action'),
dataIndex: 'action',
},
};
if (unref(getHasBatchBtn)) {
const rowSelection: any = { type: 'checkbox' };
data.rowSelection = rowSelection;
}
return data;
});
function init() {
state.config = {};
searchInfo.menuId = route.meta.modelId as string;
state.columnList = columnList;
setLoading(true);
getSearchSchemas();
getColumnList();
initViewList();
nextTick(() => {
unref(getSearchList)?.length ? searchFormSubmit() : reload({ page: 1 });
});
}
function getSearchSchemas() {
const schemas = getSearchFormSchemas(searchList);
state.searchSchemas = schemas;
schemas.forEach((cur) => {
const config = cur.__config__;
if (dyOptionsList.includes(config.yunzhupaasKey)) {
if (config.dataType === 'dictionary') {
if (!config.dictionaryType) return;
getDictionaryDataSelector(config.dictionaryType).then((res) => {
updateSchema([{ field: cur.field, componentProps: { options: res.data.list } }]);
});
}
if (config.dataType === 'dynamic') {
if (!config.propsUrl) return;
const query = { paramList: getParamList(config.templateJson) };
getDataInterfaceRes(config.propsUrl, query).then((res) => {
const data = Array.isArray(res.data) ? res.data : [];
updateSchema([{ field: cur.field, componentProps: { options: data } }]);
});
}
}
cur.defaultValue = cur.value;
});
}
function getColumnList() {
// 没有开启列表权限
let columnList = state.columnList;
state.exportList = columnList;
let columns = columnList.map((o) => ({
...o,
title: o.labelI18nCode ? t(o.labelI18nCode, o.label) : o.label,
dataIndex: o.prop,
align: o.align,
fixed: o.fixed == 'none' ? false : o.fixed,
sorter: o.sortable ? { multiple: 1 } : o.sortable,
width: o.width || 100,
}));
//添加复杂表头
columns = getComplexColumns(columns);
state.columns = columns.filter((o) => o.prop.indexOf('-') < 0);
//子表表头
getChildComplexColumns(columns);
}
//复杂表头
function getComplexColumns(columns) {
//这里生成复杂表头的配置
let complexHeaderList: any[] = [];
if (!complexHeaderList.length) return columns;
let childColumns: any[] = [];
let firstChildColumns: string[] = [];
for (let i = 0; i < complexHeaderList.length; i++) {
const e = complexHeaderList[i];
e.label = e.fullName;
e.labelI18nCode = e.fullNameI18nCode;
e.title = e.fullNameI18nCode ? t(e.fullNameI18nCode, e.fullName) : e.fullName;
e.align = e.align;
e.dataIndex = e.id;
e.prop = e.id;
e.children = [];
e.yunzhupaasKey = 'complexHeader';
if (e.childColumns?.length) {
childColumns.push(...e.childColumns);
for (let k = 0; k < e.childColumns.length; k++) {
const item = e.childColumns[k];
for (let j = 0; j < columns.length; j++) {
const o = columns[j];
if (o.prop == item && o.fixed !== 'left' && o.fixed !== 'right') e.children.push({ ...o });
}
}
}
if (e.children.length) firstChildColumns.push(e.children[0].prop);
}
complexHeaderList = complexHeaderList.filter(o => o.children.length);
let list: any[] = [];
for (let i = 0; i < columns.length; i++) {
const e = columns[i];
if (!childColumns.includes(e.prop)) {
list.push(e);
} else {
if (firstChildColumns.includes(e.prop)) {
const item = complexHeaderList.find(o => o.childColumns.includes(e.prop));
list.push(item);
}
}
}
return list;
}
//子表表头
function getChildComplexColumns(columnList) {
let list: any[] = [];
for (let i = 0; i < columnList.length; i++) {
const e = columnList[i];
if (!e.prop.includes('-')) {
list.push(e);
} else {
let prop = e.prop.split('-')[0];
let vModel = e.prop.split('-')[1];
let label = e.label.split('-')[0];
let childLabel = e.label.replace(label + '-', '');
if (e.fullNameI18nCode && Array.isArray(e.fullNameI18nCode) && e.fullNameI18nCode[0]) label = t(e.fullNameI18nCode[0], label);
let newItem = {
align: 'center',
yunzhupaasKey: 'table',
prop,
label,
title: label,
dataIndex: prop,
children: [],
};
e.dataIndex = vModel;
e.title = e.labelI18nCode ? t(e.labelI18nCode, childLabel) : childLabel;
if (!state.expandObj.hasOwnProperty(prop+`Expand`)) state.expandObj[prop+`Expand`] = false;
if (!list.some((o) => o.prop === prop)) list.push(newItem);
for (let i = 0; i < list.length; i++) {
if (list[i].prop === prop) {
list[i].children.push(e);
break;
}
}
}
}
// 行内分组展示
getMergeList(list);
state.complexColumns = list;
state.childColumnList = list.filter((o) => o.yunzhupaasKey === 'table');
// 子表分组展示宽度取100
for (let i = 0; i < state.childColumnList.length; i++) {
const e = state.childColumnList[i];
if (e.children?.length) e.children = e.children.map(o => ({ ...o, width: 100 }));
}
}
function getMergeList(list) {
list.forEach((item) => {
if (item.yunzhupaasKey === 'table' && item.children && item.children.length) {
item.children.forEach((child, index) => {
if (index == 0) {
child.customCell = () => ({
rowspan: 1,
colspan: item.children.length,
class: 'child-table-box',
});
} else {
child.customCell = () => ({
rowspan: 0,
colspan: 0,
});
}
});
}
});
}
function toggleExpand(row, field) {
row[field] = !row[field];
}
// 关联表单查看详情
function toDetail(modelId, id, propsValue) {
if (!id) return;
getConfigData(modelId).then((res) => {
if (!res.data || !res.data.formData) return;
const formConf = JSON.parse(res.data.formData);
formConf.popupType = 'general';
formConf.hasPrintBtn = false;
formConf.customBtns = [];
const data = { id, formConf, modelId, propsValue};
relationDetailRef.value?.init(data);
});
}
function handleColumnChange(data) {
state.columnSettingList = data;
}
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.editText','编辑') ,
onClick: updateHandle.bind(null, record),
},
{
label: t('common.delText','删除') ,
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
// {
// label: t('common.detailText','详情') ,
// onClick: goDetail.bind(null, record),
// },
];
}
// 编辑
function updateHandle(record) {
// 不带工作流
const data = {
id: record.id,
menuId: searchInfo.menuId,
allList: state.cacheList,
};
formRef.value?.init(data);
}
// 删除
function handleDelete(id) {
const query={ids:[id] }
batchDelete(query).then((res) => {
createMessage.success(res.msg);
clearSelectedRowKeys();
reload();
});
}
// 查看详情
function goDetail(record) {
// 不带流程
const data = {
id: record.id,
};
detailRef.value?.init(data);
}
// 新增
function addHandle() {
// 不带流程新增
const data = {
id: '',
menuId: searchInfo.menuId,
allList: state.cacheList,
};
formRef.value?.init(data);
}
// 高级查询
function handleSuperQuery(superQueryJson) {
searchInfo.superQueryJson = superQueryJson;
reload({ page: 1 });
}
function handleSearchReset() {
clearSelectedRowKeys();
if (!state.resetFromTree) updateSearchFormValue();
if (state.resetFromTree) state.resetFromTree = false;
}
function handleSearchSubmit(data) {
clearSelectedRowKeys();
let obj = {
...defaultSearchInfo,
superQueryJson: searchInfo.superQueryJson,
...data,
};
Object.keys(searchInfo).map(key => {
delete searchInfo[key];
});
for (let [key, value] of Object.entries(obj)) {
searchInfo[key.replaceAll('-', '_')] = value;
}
console.log(searchInfo);
reload({ page: 1 });
}
function updateSearchFormValue() {
if (!state.treeActiveId) return searchFormSubmit();
let queryJson: any = {};
let leftTreeActiveInfo: any = {};
const isMultiple = !state.treeRelationObj ? false : state.treeRelationObj.searchMultiple;
//多级左侧树,需要拼父级->转为查询参数
if (state.treeRelationObj && state.treeRelationObj.yunzhupaasKey && ['organizeSelect', 'cascader', 'areaSelect'].includes(state.treeRelationObj.yunzhupaasKey)) {
let currValue = [];
currValue = state.treeActiveNodePath.map(o => o[state.treeFieldNames.key]);
queryJson = { '': isMultiple ? [currValue] : currValue };
leftTreeActiveInfo = { '': state.treeRelationObj.multiple ? [currValue] : currValue };
} else {
queryJson = { '': isMultiple ? [state.treeActiveId] : state.treeActiveId };
leftTreeActiveInfo = { '': state.treeRelationObj.multiple ? [state.treeActiveId] : state.treeActiveId };
}
state.leftTreeActiveInfo = leftTreeActiveInfo;
if(unref(getSearchList)?.length){
// 有搜索列表
setFieldsValue(queryJson);
searchFormSubmit();
}else{
// 无搜索列表
handleSearchSubmit(queryJson);
}
}
function initViewList(currentId = '') {
const query = {
menuId: route.meta.modelId,
};
getViewList(query).then(res => {
const columns : any[]= state.complexColumns;
const searchList: any[] = state.searchSchemas.map(o => ({ label: o.label, id: o.field, show: o.show, labelI18nCode: o.labelI18nCode }));
const columnList: any[] = columns.map(o => ({ label: o.label, id: o.prop, show: true, fixed: o.fixed || 'none', labelI18nCode: o.labelI18nCode }));
state.viewList = (res.data || []).map(o => {
if (o.type == 0) return { ...o, searchList, columnList };
return { ...o, searchList: o.searchList ? JSON.parse(o.searchList) : [], columnList: o.columnList ? JSON.parse(o.columnList) : [] };
});
if (currentId) {
state.currentView = state.viewList.filter(o => o.id === currentId)[0] || state.viewList[0];
} else {
state.currentView = state.viewList.filter(o => o.status === 1)[0] || state.viewList[0];
}
});
}
function handleViewClick(item) {
state.currentView = item;
}
function setListValue(data: any[] = [], defaultData: any[] = [], key) {
let list: any[] = [];
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < defaultData.length; j++) {
if (data[i].show && data[i].id == defaultData[j][key]) list.push(defaultData[j]);
}
}
return list;
}
onMounted(() => {
init();
});
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div class="yunzhupaas-content-wrapper bg-white yunzhupaas-extend-barCode">
<div class="yunzhupaas-content-wrapper-center">
<a-tabs v-model:activeKey="activeKey" class="yunzhupaas-content-wrapper-tabs" destroyInactiveTabPane>
<a-tab-pane key="1" tab="生成二维码">
<a-row class="mt-20px">
<a-col :span="16">
<a-form :colon="false" :labelCol="{ style: { width: '100px' } }">
<a-form-item label="二维码内容">
<a-input v-model:value="qrcode" placeholder="输入要生成二维码的字符串">
<template #addonAfter>
<span class="cursor-pointer" @click="getQrcode">生成</span>
</template>
</a-input>
</a-form-item>
<a-form-item label="二维码图像">
<canvas id="qrcode" ref="qrCodeRef"></canvas>
<p class="tips">使用微信扫一扫</p>
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane key="2" tab="生成条形码">
<a-row class="mt-20px">
<a-col :span="16">
<a-form :colon="false" :labelCol="{ style: { width: '100px' } }">
<a-form-item label="条形码内容">
<a-input v-model:value="barcode" placeholder="输入要生成条形码的字符串">
<template #addonAfter>
<span class="cursor-pointer" @click="getBarcode">生成</span>
</template>
</a-input>
</a-form-item>
<a-form-item label="条形码图像">
<canvas id="barcode"></canvas>
<p class="tips">使用微信扫一扫</p>
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, ref } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import JsBarcode from 'jsbarcode';
import { toCanvas } from 'qrcode';
defineOptions({ name: 'extend-barCode' });
const state = reactive({
activeKey: '1',
barcode: '',
qrcode: '',
});
const { activeKey, barcode, qrcode } = toRefs(state);
const qrCodeRef = ref();
const { createMessage } = useMessage();
function getQrcode() {
if (!state.qrcode) return createMessage.error('请输入二维码内容');
toCanvas(qrCodeRef.value, state.qrcode, {
margin: 0,
width: 265,
});
}
function getBarcode() {
let reg = /^[A-Za-z0-9]+$/;
if (!reg.test(state.barcode)) return createMessage.error('请输入数字或者英文字母');
JsBarcode('#barcode', state.barcode, {
width: 4,
height: 80,
displayValue: false,
});
}
</script>
<style lang="less" scoped>
#qrcode {
width: 265px;
height: 265px;
border: 1px solid @border-color-base1;
}
#barcode {
width: 265px;
height: 80px;
border: 1px solid @border-color-base1;
}
.tips {
padding: 8px 0;
color: @text-color-label;
}
</style>

View File

@@ -0,0 +1,79 @@
<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()" :loading="loading">{{ t('common.addText') }}</a-button>
</template>
</BasicTable>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getBigDataList, createBigData } from '@/api/extend/bigData';
import { BasicTable, useTable, BasicColumn } from '@/components/Table';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
defineOptions({ name: 'extend-bigData' });
const { createMessage, createConfirm } = useMessage();
const { t } = useI18n();
const loading = ref(false);
const columns: BasicColumn[] = [
{
title: '编码',
dataIndex: 'enCode',
},
{
title: '名称',
dataIndex: 'fullName',
},
{
title: '创建时间',
dataIndex: 'creatorTime',
format: 'date|YYYY-MM-DD HH:mm:ss',
},
];
const [registerTable, { reload }] = useTable({
api: getBigDataList,
columns,
useSearchForm: true,
formConfig: {
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
],
},
});
function addOrUpdateHandle() {
createConfirm({
iconType: 'warning',
title: t('common.tipTitle'),
content: '您确定要创建10000条数据吗, 是否继续?',
onOk: () => {
loading.value = true;
createBigData()
.then(res => {
createMessage.success(res.msg);
loading.value = false;
reload();
})
.catch(() => {
loading.value = false;
});
},
});
}
</script>

View File

@@ -0,0 +1,97 @@
<template>
<a-modal
v-model:open="visible"
:footer="null"
:closable="false"
:keyboard="false"
:maskClosable="false"
class="common-container-modal yunzhupaas-full-modal full-modal file-preview-modal"
wrap-class-name="fullscreen-modal">
<template #closeIcon>
<ModalClose :canFullscreen="false" @cancel="handleCancel" />
</template>
<template #title>
<div class="yunzhupaas-full-modal-header">
<div class="header-title">
<p class="header-txt">{{ title }}</p>
</div>
<a-space class="options" :size="10">
<a-button @click="handleCancel()">{{ t('common.cancelText') }}</a-button>
</a-space>
</div>
</template>
<div class="basic-content bg-white" v-loading="loading">
<iframe width="100%" height="100%" :src="url" frameborder="0"></iframe>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { reactive, toRefs } from 'vue';
import { Modal as AModal } from 'ant-design-vue';
import ModalClose from '@/components/Modal/src/components/ModalClose.vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
import { previewFile } from '@/api/extend/documentPreview';
import { getToken } from '@/utils/auth';
import { useGlobSetting } from '@/hooks/setting';
import { encryptByBase64 } from '@/utils/cipher';
interface State {
visible: boolean;
loading: boolean;
title: string;
url: string;
}
const { createMessage } = useMessage();
const { t } = useI18n();
const globSetting = useGlobSetting();
const state = reactive<State>({
visible: false,
loading: false,
title: '',
url: '',
});
const { visible, loading, title, url } = toRefs(state);
defineExpose({ init });
function init(data) {
state.title = '文档预览 - ' + data.name;
state.url = '';
if (!data.id) return (state.visible = false);
state.visible = true;
state.loading = true;
previewFile(data.id, data.type)
.then(res => {
state.loading = false;
if (res.data) {
if (data.type === 'localPreview') {
state.url = `${globSetting.filePreviewServer}/onlinePreview?url=` + encodeURIComponent(encryptByBase64(res.data)) + '&token=' + getToken();
return;
}
state.url = res.data;
} else {
createMessage.warning('文件不存在');
handleCancel();
}
})
.catch(() => {
state.loading = false;
handleCancel();
});
}
function handleCancel() {
state.visible = false;
}
</script>
<style lang="less">
.file-preview-modal {
.ant-modal-body {
padding: 10px !important;
}
.header-txt {
max-width: 80vw !important;
}
}
</style>

View File

@@ -0,0 +1,103 @@
<template>
<div class="yunzhupaas-content-wrapper documentPreview-wrapper">
<div class="yunzhupaas-content-wrapper-center">
<div class="yunzhupaas-content-wrapper-search-box">
<BasicForm class="search-form" @register="registerForm" @submit="handleSubmit" @reset="handleReset" />
</div>
<div class="yunzhupaas-content-wrapper-content bg-white">
<a-tabs v-model:activeKey="activeKey" class="yunzhupaas-content-wrapper-tabs" destroyInactiveTabPane>
<a-tab-pane key="localPreview" tab="本地预览"></a-tab-pane>
<a-tab-pane key="yozoOnlinePreview" tab="在线预览"></a-tab-pane>
</a-tabs>
<div class="p-10px">
<a-alert message="本地预览支持doc/docx/xls/xlsx/ppt/pptx/pdf等办公文档。" type="warning" show-icon v-if="activeKey === 'localPreview'" />
<a-alert message="免责声明永中文档预览组件不属于yunzhupaas产品只用于介绍第三方组件如何在《云筑项目管理平台》中使用。" type="warning" show-icon v-else />
</div>
<BasicTable @register="registerTable" :searchInfo="getSearchInfo">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileName'">
<p class="link-text" @click="handleView(record.fileId, record.fileName)">{{ record.fileName }}</p>
</template>
</template>
</BasicTable>
</div>
</div>
<Preview ref="filePreviewRef" />
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, toRefs, computed, nextTick } from 'vue';
import { getDocumentPreviewList } from '@/api/extend/documentPreview';
import { BasicForm, useForm } from '@/components/Form';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicTable, useTable } from '@/components/Table';
import Preview from './Preview.vue';
defineOptions({ name: 'extend-documentPreview' });
interface State {
activeKey: string;
keyword: string;
}
const { t } = useI18n();
const filePreviewRef = ref<any>(null);
const state = reactive<State>({
activeKey: 'localPreview',
keyword: '',
});
const { activeKey } = toRefs(state);
const getSearchInfo = computed(() => ({ keyword: state.keyword }));
const [registerForm] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
],
});
const [registerTable, { reload }] = useTable({
api: getDocumentPreviewList,
columns: [
{ title: '文件名称', dataIndex: 'fileName' },
{ title: '文件类型', dataIndex: 'fileType', width: 150 },
{ title: '文件大小', dataIndex: 'fileSize', width: 150 },
],
pagination: false,
showTableSetting: false,
});
function handleSubmit(values) {
state.keyword = values?.keyword || '';
handleSearch();
}
function handleReset() {
state.keyword = '';
handleSearch();
}
function handleSearch() {
nextTick(() => {
reload();
});
}
function handleView(id, name) {
const data = {
id,
name,
type: state.activeKey,
};
filePreviewRef.value?.init(data);
}
</script>

View File

@@ -0,0 +1,129 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="邮箱配置" showOkBtn @ok="handleSubmit" destroyOnClose>
<BasicForm @register="registerForm">
<template #password="{ model, field }">
<a-input v-model:value="model[field]" allowClear placeholder="邮箱密码">
<template #addonAfter>
<loading-outlined class="mr-5px" v-if="state.testLoading" />
<span class="cursor-pointer" disabled @click="test()">连接测试</span>
</template>
</a-input>
</template>
</BasicForm>
</BasicModal>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm, FormSchema } from '@/components/Form';
import { getConfigInfo, saveConfig, checkMail } from '@/api/extend/email';
import { useMessage } from '@/hooks/web/useMessage';
import { LoadingOutlined } from '@ant-design/icons-vue';
interface State {
testLoading: boolean;
}
const state = reactive<State>({
testLoading: false,
});
const schemas: FormSchema[] = [
{
field: 'pop3Host',
label: 'POP3服务',
component: 'Input',
componentProps: { placeholder: '请输入' },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'pop3Port',
label: 'POP3端口',
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'smtpHost',
label: 'SMTP服务',
component: 'Input',
componentProps: { placeholder: '请输入' },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'smtpPort',
label: 'SMTP端口',
component: 'InputNumber',
componentProps: { min: 0, max: 999999 },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'senderName',
label: '显示名称',
component: 'Input',
componentProps: { placeholder: '请输入' },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'account',
label: '邮箱地址',
component: 'Input',
componentProps: { placeholder: '请输入' },
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'password',
label: '邮箱密码',
component: 'InputSearch',
slot: 'password',
rules: [{ required: true, trigger: 'blur', message: '必填' }],
},
{
field: 'emailSsl',
label: 'SSL登录',
component: 'Switch',
defaultValue: 0,
},
];
defineEmits(['register']);
const { createMessage } = useMessage();
const [registerForm, { setFieldsValue, validate, resetFields, clearValidate }] = useForm({ labelWidth: 80, schemas: schemas });
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner(init);
function init() {
state.testLoading = false;
resetFields();
changeLoading(true);
getConfigInfo().then(res => {
setFieldsValue(res.data);
clearValidate();
changeLoading(false);
});
}
async function test() {
const values = await validate();
if (!values) return;
state.testLoading = true;
checkMail(values)
.then(res => {
createMessage.success(res.msg);
state.testLoading = false;
})
.catch(() => {
state.testLoading = false;
});
}
async function handleSubmit() {
const values = await validate();
if (!values) return;
changeOkLoading(true);
saveConfig(values)
.then(res => {
createMessage.success(res.msg);
changeOkLoading(false);
closeModal();
})
.catch(() => {
changeOkLoading(false);
});
}
</script>

View File

@@ -0,0 +1,35 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" :title="isSend ? '查看邮件 - 已发送' : '查看邮件 - 收件箱'">
<DetailMain :dataForm="dataForm" :isSend="isSend" />
</BasicPopup>
</template>
<script lang="ts" setup>
import { reactive, toRefs } from 'vue';
import { BasicPopup, usePopupInner } from '@/components/Popup';
import { getEmailInfo } from '@/api/extend/email';
import DetailMain from './DetailMain.vue';
interface State {
dataForm: any;
isSend: boolean;
}
defineEmits(['register']);
const state = reactive<State>({
dataForm: {},
isSend: false,
});
const { dataForm, isSend } = toRefs(state);
const [registerPopup, { changeLoading }] = usePopupInner(init);
function init(data) {
state.isSend = !!data.isSend;
if (data.id) {
changeLoading(true);
getEmailInfo(data.id).then(res => {
state.dataForm = res.data;
changeLoading(false);
});
}
}
</script>

View File

@@ -0,0 +1,73 @@
<template>
<a-form :colon="false" labelAlign="right" :labelCol="{ style: { width: '60px' } }" :model="dataForm" ref="formElRef" class="!px-10px">
<a-form-item name="subject">
<h4 class="text">{{ dataForm.subject }}</h4>
</a-form-item>
<a-divider></a-divider>
<a-form-item label="发件人">
<p class="text recipient">{{ isSend ? dataForm.sender : dataForm.senderName + '&lt;' + dataForm.sender + '>' }} </p>
</a-form-item>
<a-divider></a-divider>
<a-form-item label="时间">
<p class="text" v-if="isSend">{{ formatToDateTime(dataForm.creatorTime) }}</p>
<p class="text" v-if="!isSend">{{ formatToDateTime(dataForm.fdate) }}</p>
</a-form-item>
<a-divider></a-divider>
<a-form-item label="收件人">
<p class="text">{{ isSend ? dataForm.recipient : dataForm.mAccount }}</p>
</a-form-item>
<a-divider></a-divider>
<template v-if="dataForm.cc">
<a-form-item label="抄送人" name="cc">
<p>{{ dataForm.cc }}</p>
</a-form-item>
<a-divider></a-divider>
</template>
<template v-if="dataForm.bcc">
<a-form-item label="密送人" name="bcc">
<p class="text">{{ dataForm.bcc }}</p>
</a-form-item>
<a-divider></a-divider>
</template>
<template v-if="getFileList.length">
<a-form-item label="附件" name="attachment">
<yunzhupaas-upload-file v-model:value="getFileList" type="mail" detailed disabled />
</a-form-item>
<a-divider></a-divider>
</template>
<a-form-item name="bodyText">
<p class="content" v-html="dataForm.bodyText"></p>
</a-form-item>
</a-form>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { formatToDateTime } from '@/utils/dateUtil';
const props = defineProps(['dataForm', 'isSend']);
defineEmits(['register']);
const formElRef = ref<FormInstance>();
const getFileList = computed(() => (props.dataForm.attachment ? JSON.parse(props.dataForm.attachment) : []));
</script>
<style lang="less" scoped>
.ant-form {
:deep(.ant-form-item) {
margin-bottom: 0;
}
.ant-divider-horizontal {
margin: 5px 0;
}
.text {
word-break: break-all;
&.recipient {
color: @success-color;
}
}
h4.text {
font-size: 15px;
margin-bottom: 0;
}
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<div class="yunzhupaas-content-wrapper bg-white" v-loading="loading">
<DetailMain :dataForm="dataForm" :isSend="isSend" class="overflow-auto" />
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onMounted } from 'vue';
import { getEmailInfo } from '@/api/extend/email';
import DetailMain from './DetailMain.vue';
import { useRoute } from 'vue-router';
defineOptions({ name: 'extend-email' });
interface State {
dataForm: any;
isSend: boolean;
loading: boolean;
}
defineEmits(['register']);
const state = reactive<State>({
dataForm: {},
isSend: false,
loading: false,
});
const route = useRoute();
const { dataForm, isSend, loading } = toRefs(state);
function init() {
state.loading = true;
const id = route.query.id;
if (!id) return (state.loading = false);
getEmailInfo(id).then(res => {
state.dataForm = res.data;
state.loading = false;
});
}
onMounted(() => {
init();
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,151 @@
<template>
<BasicPopup v-bind="$attrs" @register="registerPopup" title="写邮件">
<template #insertToolbar>
<a-button type="primary" @click="handleSubmit(true)" :loading="sendLoading" :disabled="saveLoading">发送</a-button>
<a-button type="warning" @click="handleSubmit()" :loading="saveLoading" :disabled="sendLoading" class="ml-10px">草稿</a-button>
</template>
<a-form :colon="false" labelAlign="right" :labelCol="{ style: { width: '60px' } }" :model="dataForm" :rules="rules" ref="formElRef" class="!px-10px">
<a-form-item label="收件人" name="recipient">
<a-select v-model:value="dataForm.recipient" mode="tags" :token-separators="[',']" placeholder="收件人" :open="false" />
</a-form-item>
<a-form-item label="抄送人" name="cc" v-if="showCC">
<a-select v-model:value="dataForm.cc" mode="tags" :token-separators="[',']" placeholder="抄送人" :open="false" />
</a-form-item>
<a-form-item label="密送人" name="bcc" v-if="showBCC">
<a-select v-model:value="dataForm.bcc" mode="tags" :token-separators="[',']" placeholder="密送人" :open="false" />
</a-form-item>
<a-form-item label=" ">
<span class="link-text" @click="toggleCC">{{ showCC ? '删除抄送' : '添加抄送' }}</span>
&nbsp;-&nbsp;
<span class="link-text" @click="toggleBCC">{{ showBCC ? '删除密送' : '添加密送' }} </span>
</a-form-item>
<a-form-item label="主题" name="subject">
<a-input v-model:value="dataForm.subject" placeholder="输入主题" allowClear />
</a-form-item>
<a-form-item label="附件" name="attachment">
<yunzhupaas-upload-file v-model:value="fileList" type="mail" />
</a-form-item>
<a-form-item label="正文" name="bodyText">
<yunzhupaas-editor v-model:value="dataForm.bodyText" />
</a-form-item>
</a-form>
</BasicPopup>
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs } from 'vue';
import { BasicPopup, usePopupInner } from '@/components/Popup';
import { saveSent, saveDraft, getEmailInfo } from '@/api/extend/email';
import { useMessage } from '@/hooks/web/useMessage';
import type { FormInstance } from 'ant-design-vue';
interface State {
dataForm: any;
rules: any;
sendLoading: boolean;
saveLoading: boolean;
showCC: boolean;
showBCC: boolean;
fileList: any[];
}
const emit = defineEmits(['register', 'reload']);
const formElRef = ref<FormInstance>();
const state = reactive<State>({
dataForm: {
recipient: [],
cc: [],
bcc: [],
subject: '',
bodyText: '',
id: '',
attachment: '',
},
rules: {
recipient: [{ type: 'array', required: true, message: '收件人不能为空', trigger: 'blur' }],
cc: [{ type: 'array', required: true, message: '抄送人不能为空', trigger: 'blur' }],
bcc: [{ type: 'array', required: true, message: '密送人不能为空', trigger: 'blur' }],
subject: [{ required: true, message: '主题不能为空', trigger: 'blur' }],
},
sendLoading: false,
saveLoading: false,
showCC: false,
showBCC: false,
fileList: [],
});
const { dataForm, rules, sendLoading, saveLoading, showCC, showBCC, fileList } = toRefs(state);
const { createMessage } = useMessage();
const [registerPopup, { closePopup, changeLoading }] = usePopupInner(init);
function init(data) {
formElRef.value?.resetFields();
resetData();
state.showCC = false;
state.showBCC = false;
state.sendLoading = false;
state.saveLoading = false;
state.fileList = [];
if (data.id) {
changeLoading(true);
getEmailInfo(data.id).then(res => {
state.dataForm = {
recipient: res.data.recipient ? res.data.recipient.split(',') : [],
cc: res.data.cc ? res.data.cc.split(',') : [],
bcc: res.data.bcc ? res.data.bcc.split(',') : [],
subject: res.data.subject,
bodyText: res.data.bodyText,
id: res.data.id,
};
state.showCC = !!state.dataForm.cc.length;
state.showBCC = !!state.dataForm.bcc.length;
state.fileList = res.data.attachment ? JSON.parse(res.data.attachment) : [];
changeLoading(false);
});
}
}
function toggleCC() {
state.showCC = !state.showCC;
state.dataForm.cc = [];
}
function toggleBCC() {
state.showBCC = !state.showBCC;
state.dataForm.bcc = [];
}
async function handleSubmit(isSend = false) {
try {
const values = await formElRef.value?.validate();
if (!values) return;
isSend ? (state.sendLoading = true) : (state.saveLoading = true);
let data: any = {
recipient: state.dataForm.recipient.join(','),
subject: state.dataForm.subject,
bodyText: state.dataForm.bodyText,
attachment: JSON.stringify(state.fileList),
};
if (state.showCC) data = { ...data, cc: state.dataForm.cc.join(',') };
if (state.showBCC) data = { ...data, bcc: state.dataForm.bcc.join(',') };
if (state.dataForm.id) data = { ...data, id: state.dataForm.id };
const formMethod = isSend ? saveSent : saveDraft;
formMethod(data)
.then(res => {
createMessage.success(res.msg);
isSend ? (state.sendLoading = false) : (state.saveLoading = false);
closePopup();
emit('reload');
})
.catch(() => {
isSend ? (state.sendLoading = false) : (state.saveLoading = false);
});
} catch (_) {}
}
function resetData() {
state.dataForm = {
recipient: [],
cc: [],
bcc: [],
subject: '',
bodyText: '',
id: '',
attachment: '',
};
}
</script>

View File

@@ -0,0 +1,325 @@
<template>
<div class="yunzhupaas-content-wrapper bg-white">
<div class="yunzhupaas-content-wrapper-center email-wrapper">
<a-tabs v-model:activeKey="activeKey" tab-position="left" class="common-left-tabs">
<template #leftExtra>
<div class="tab-tem" @click="openFormPopup(true, { id: '' })"><i class="icon-ym icon-ym-btn-edit"></i>写邮件</div>
</template>
<a-tab-pane key="inBox">
<template #tab><i class="icon-ym icon-ym-extend-envelope"></i>收件箱</template>
</a-tab-pane>
<a-tab-pane key="star">
<template #tab><i class="icon-ym icon-ym-extend-star"></i>星标件</template>
</a-tab-pane>
<a-tab-pane key="draft">
<template #tab><i class="icon-ym icon-ym-extend-exclamation-triangle"></i>草稿箱</template>
</a-tab-pane>
<a-tab-pane key="sent">
<template #tab><i class="icon-ym icon-ym-extend-paper-plane"></i>已发送</template>
</a-tab-pane>
<template #rightExtra>
<div class="tab-tem mt-2px" @click="openConfigModal(true, {})"><i class="icon-ym icon-ym-extend-cog"></i>邮箱配置</div>
</template>
</a-tabs>
<div class="email-container">
<div class="yunzhupaas-common-search-box">
<BasicForm class="search-form" @register="registerSearchForm" @submit="handleSubmit" @reset="handleReset"></BasicForm>
<div class="yunzhupaas-common-search-box-right">
<a-button type="primary" preIcon="icon-ym icon-ym-btn-download" @click="receiveEmail" :loading="state.btnLoading">收邮件</a-button>
</div>
</div>
<BasicTable @register="registerTable" :searchInfo="getSearchInfo" :columns="getColumns">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'isRead'">
<span v-if="activeKey === 'inBox' || activeKey === 'star'">
<span v-if="record.isRead">
<i class="icon-ym icon-ym-extend-envelope-open-o i-default" title="点击标记为未读" @click="handleSetUnread(record)"></i>
</span>
<span v-else><i class="icon-ym icon-ym-extend-envelope text-warning" title="点击标记为已读" @click="handleSetRead(record)"></i></span>
</span>
<span v-else><i class="icon-ym icon-ym-extend-envelope-open-o i-default"></i></span>
</template>
<template v-if="column.key === 'attachment'">
<span v-if="record.attachment && JSON.parse(record.attachment).length"><i class="icon-ym icon-ym-extend-paperclip i-default"></i></span>
<span v-else></span>
</template>
<template v-if="column.key === 'subject'">
<p class="link-text" @click="updateEmail(record.id)" v-if="activeKey === 'draft'">{{ record.subject }}</p>
<p class="link-text" @click="readInfo(record, activeKey === 'sent')" v-else>{{ record.subject }}</p>
</template>
<template v-if="column.key === 'starred'">
<span v-if="record.starred">
<i class="icon-ym icon-ym-extend-star text-warning img-star" title="点击取消星标" @click="handleSetUnStar(record)"></i>
</span>
<span v-else><i class="icon-ym icon-ym-extend-star-o i-default img-star" title="点击标记为星标邮件" @click="handleSetStar(record)"></i></span>
</template>
<template v-if="column.key === 'action'">
<TableAction :actions="getTableActions(record)" />
</template>
</template>
</BasicTable>
</div>
</div>
<Config @register="registerConfigModal" />
<Form @register="registerForm" @reload="refresh" />
<Detail @register="registerDetail" />
</div>
</template>
<script setup lang="ts">
import { getEmailList, delEmail, receive, setRead, setUnread, setStar, setUnStar } from '@/api/extend/email';
import { computed, reactive, toRefs, onMounted, nextTick, watch } from 'vue';
import { useModal } from '@/components/Modal';
import { usePopup } from '@/components/Popup';
import { BasicForm, useForm } from '@/components/Form';
import { BasicTable, useTable, TableAction, ActionItem } from '@/components/Table';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import Config from './Config.vue';
import Form from './Form.vue';
import Detail from './Detail.vue';
import dayjs from 'dayjs';
interface State {
activeKey: string;
keyword: string;
startTime: number;
endTime: number;
btnLoading: boolean;
}
defineOptions({ name: 'extend-email' });
const { t } = useI18n();
const { createMessage } = useMessage();
const state = reactive<State>({
activeKey: 'inBox',
keyword: '',
startTime: 0,
endTime: 0,
btnLoading: false,
});
const getSearchInfo = computed(() => ({ keyword: state.keyword, type: state.activeKey, startTime: state.startTime || null, endTime: state.endTime || null }));
const getColumns = computed<any[]>(() => {
if (state.activeKey === 'inBox' || state.activeKey === 'star') {
return [
{ title: '', dataIndex: 'isRead', width: 40, align: 'center' },
{ title: '', dataIndex: 'attachment', width: 40, align: 'center' },
{ title: '发件人', dataIndex: 'sender', width: 180 },
{ title: '主题', dataIndex: 'subject' },
{ title: '时间', dataIndex: 'fdate', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
{ title: '', dataIndex: 'starred', width: 40, align: 'center' },
];
}
return [
{ title: '', dataIndex: 'isRead', width: 40, align: 'center' },
{ title: '', dataIndex: 'attachment', width: 40, align: 'center' },
{ title: '收件人', dataIndex: 'recipient', width: 180 },
{ title: '主题', dataIndex: 'subject' },
{ title: '时间', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
];
});
watch(
() => state.activeKey,
() => {
nextTick(() => resetFields());
},
);
const { activeKey } = toRefs(state);
const [registerConfigModal, { openModal: openConfigModal }] = useModal();
const [registerForm, { openPopup: openFormPopup }] = usePopup();
const [registerDetail, { openPopup: openDetailPopup }] = usePopup();
const [registerSearchForm, { resetFields }] = useForm({
baseColProps: { span: 6 },
showActionButtonGroup: true,
showAdvancedButton: true,
compact: true,
schemas: [
{
field: 'keyword',
label: t('common.keyword'),
component: 'Input',
componentProps: {
placeholder: t('common.enterKeyword'),
submitOnPressEnter: true,
},
},
{
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: ['开始时间', '结束时间'],
},
},
],
fieldMapToTime: [['pickerVal', ['startTime', 'endTime']]],
});
const [registerTable, { reload, deleteTableDataRecord }] = useTable({
api: getEmailList,
showTableSetting: false,
immediate: false,
resizeHeightOffset: 10,
actionColumn: {
width: 50,
title: '操作',
dataIndex: 'action',
},
});
function getTableActions(record): ActionItem[] {
return [
{
label: t('common.delText'),
color: 'error',
modelConfirm: {
onOk: handleDelete.bind(null, record.id),
},
},
];
}
function handleDelete(id) {
delEmail(id).then(res => {
createMessage.success(res.msg).then(() => {
reload();
});
});
}
function handleSubmit(values) {
state.keyword = values?.keyword || '';
state.startTime = values?.startTime || 0;
state.endTime = values?.endTime || 0;
handleSearch();
}
function handleReset() {
state.keyword = '';
state.startTime = 0;
state.endTime = 0;
handleSearch();
}
function handleSearch() {
nextTick(() => {
reload();
});
}
function receiveEmail() {
state.btnLoading = true;
receive()
.then(res => {
state.btnLoading = false;
createMessage.success(`收件成功${res.data}`);
if (state.activeKey == 'inBox') {
resetFields();
} else {
state.activeKey = 'inBox';
}
})
.catch(() => {
state.btnLoading = false;
});
}
function handleSetUnread(record) {
setUnread(record.id).then(res => {
record.isRead = 0;
createMessage.success(res.msg);
});
}
function handleSetRead(record) {
setRead(record.id).then(res => {
record.isRead = 1;
createMessage.success(res.msg);
});
}
function handleSetUnStar(record) {
setUnStar(record.id).then(res => {
if (state.activeKey === 'star') {
deleteTableDataRecord(record.id);
} else {
record.starred = 0;
}
createMessage.success(res.msg);
});
}
function handleSetStar(record) {
setStar(record.id).then(res => {
record.starred = 1;
createMessage.success(res.msg);
});
}
function updateEmail(id) {
openFormPopup(true, { id });
}
function readInfo(record, isSend) {
openDetailPopup(true, { id: record.id, isSend });
if (!isSend) record.isRead = 1;
}
function refresh(isSend) {
if (isSend) {
if (state.activeKey === 'sent') {
resetFields();
} else {
state.activeKey = 'sent';
}
} else {
if (state.activeKey === 'draft') {
resetFields();
} else {
state.activeKey = 'draft';
}
}
}
onMounted(() => {
resetFields();
});
</script>
<style lang="less" scoped>
.email-wrapper {
flex-direction: row;
:deep(.ant-table-container) {
.ant-table-cell::before {
display: none !important;
}
}
:deep(.common-left-tabs) {
.ant-tabs-nav {
padding-top: 10px;
}
.ant-tabs-nav-wrap {
flex: unset;
.ant-tabs-nav-list {
padding-top: 0 !important;
}
}
}
.tab-tem {
width: 160px;
padding: 12px 24px;
cursor: pointer;
.icon-ym {
font-size: 14px;
margin-right: 8px;
}
}
.yunzhupaas-common-search-box {
margin-bottom: 10px;
.yunzhupaas-common-search-box-right {
top: 10px;
}
}
.email-container {
padding-top: 10px;
height: 100%;
overflow: hidden;
.icon-ym {
font-size: 14px;
cursor: pointer;
}
}
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class="yunzhupaas-content-wrapper yunzhupaas-content-wrapper-form">
<div class="yunzhupaas-content-wrapper-form-body px-10px">
<ScrollContainer>
<div class="my-10px">
<a-alert message="普通文本、普通数值、日期时间主键的使用" type="warning" :show-icon="false" />
</div>
<a-form ref="formRef" :colon="false" :model="dataForm" :labelCol="{ style: { width: '110px' } }">
<a-divider orientation="left">普通文本框的使用</a-divider>
<a-form-item label="普通文本框">
<a-input v-model:value="dataForm.CommonText" placeholder="" />
</a-form-item>
<a-form-item label="座机号码">
<a-input v-model:value="dataForm.TelePhone" placeholder="" />
</a-form-item>
<a-form-item label="当前登录人">
<a-input v-model:value="dataForm.RealName" placeholder="" />
</a-form-item>
<a-divider orientation="left">普通数值框的使用</a-divider>
<a-form-item label="自然数">
<a-input-number v-model:value="dataForm.Natural" placeholder="" />
</a-form-item>
<a-form-item label="报销款">
<a-input-number v-model:value="dataForm.Reimbursement" placeholder="" addon-after="" />
</a-form-item>
<a-form-item label="产品价格">
<a-input-number v-model:value="dataForm.ProductPrice" placeholder="">
<template #addonAfter>
<span class="cursor-pointer" @click="handleRandomValue">随机设定</span>
</template>
</a-input-number>
</a-form-item>
<a-divider orientation="left">日期时间组件的使用</a-divider>
<a-form-item label="生产日期">
<yunzhupaas-date-picker v-model:value="dataForm.ProductionDate" format="YYYY-MM-DD" allowClear />
</a-form-item>
<a-form-item label="回款日期">
<yunzhupaas-date-picker v-model:value="dataForm.ReturnMoneyDate" format="YYYY-MM-DD HH:mm:ss" allowClear />
</a-form-item>
<a-form-item label="计划执行日期">
<yunzhupaas-date-picker v-model:value="dataForm.PlanExecutionDate" format="YYYY-MM-DD HH:mm:ss" allowClear />
</a-form-item>
<a-form-item label="统计月份">
<yunzhupaas-date-picker v-model:value="dataForm.StatisticalMonth" format="YYYY-MM" allowClear />
</a-form-item>
<a-form-item label="规划日期">
<yunzhupaas-date-picker v-model:value="dataForm.Programme" format="YYYY-MM-DD" allowClear />
</a-form-item>
<a-form-item label="规划日期">
<yunzhupaas-date-range v-model:value="dataForm.Dates" format="YYYY-MM-DD HH:mm:ss" allowClear />
</a-form-item>
</a-form>
</ScrollContainer>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, ref } from 'vue';
import { ScrollContainer } from '@/components/Container';
import type { FormInstance } from 'ant-design-vue';
defineOptions({ name: 'extend-formDemo-verifyForm1' });
const state = reactive({
dataForm: {
CommonText: '',
TelePhone: '0000-00000000',
RealName: '',
Natural: '',
Reimbursement: '',
ProductPrice: '',
ProductionDate: undefined,
ReturnMoneyDate: undefined,
PlanExecutionDate: undefined,
StatisticalMonth: undefined,
Programme: undefined,
Dates: [],
},
});
const { dataForm } = toRefs(state);
const formRef = ref<FormInstance>();
function handleRandomValue() {
let num = GetRandomNum(1, 100);
state.dataForm.ProductPrice = num;
}
function GetRandomNum(Min, Max) {
let Range = Max - Min;
let Rand = Math.random();
return Min + Math.round(Rand * Range);
}
</script>
<style lang="less" scoped>
.ant-input,
.ant-picker,
.ant-input-number,
.ant-input-number-group-wrapper {
width: 300px !important;
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="yunzhupaas-content-wrapper yunzhupaas-content-wrapper-form">
<div class="yunzhupaas-common-page-header">
<BasicTitle>表单示例</BasicTitle>
<a-space>
<a-button type="primary" :loading="btnLoading" @click="handleSubmit">{{ t('common.okText') }}</a-button>
<a-button @click="resetFields">{{ t('common.resetText') }}</a-button>
</a-space>
</div>
<div class="yunzhupaas-content-wrapper-form-body">
<ScrollContainer v-loading="loading">
<div class="p-10px">
<BasicForm @register="registerForm" />
</div>
</ScrollContainer>
</div>
</div>
</template>
<script lang="ts" setup>
import { getDictionaryType } from '@/api/systemData/dictionary';
import { ref } from 'vue';
import { Space as ASpace } from 'ant-design-vue';
import { ScrollContainer } from '@/components/Container';
import { BasicTitle } from '@/components/Basic';
import { BasicForm, useForm } from '@/components/Form';
import { getFormSchema } from './schemaData';
import { useMessage } from '@/hooks/web/useMessage';
import { useI18n } from '@/hooks/web/useI18n';
defineOptions({ name: 'extend-formDemo' });
const { createMessage } = useMessage();
const { t } = useI18n();
const loading = ref(false);
const btnLoading = ref(false);
const [registerForm, { resetFields, validate, updateSchema }] = useForm({
labelWidth: 100,
labelAlign: 'right',
schemas: getFormSchema(),
});
function initData() {
getDictionaryType().then(res => {
const treeData = res.data.list;
updateSchema({ field: 'fieldTreeSelect', componentProps: { options: treeData } });
updateSchema({ field: 'fieldCascader', componentProps: { options: treeData } });
});
}
async function handleSubmit() {
const values = await validate();
if (!values) return;
btnLoading.value = true;
setTimeout(() => {
btnLoading.value = false;
}, 1000);
createMessage.success('请在控制台查看!');
}
initData();
</script>

View File

@@ -0,0 +1,275 @@
import { FormSchema } from '@/components/Form';
export const getFormSchema = (): FormSchema[] => {
const schemas: FormSchema[] = [
{
field: 'fieldInput',
label: '单行输入',
component: 'Input',
helpMessage: '单行输入',
},
{
field: 'fieldInput2',
label: '单行输入',
component: 'Input',
componentProps: { showPassword: true },
},
{
field: 'fieldTextarea',
label: '多行输入',
component: 'Textarea',
},
{
field: 'fieldInputNumber',
label: '数字输入',
component: 'InputNumber',
componentProps: {
min: 0,
max: 999999,
},
},
{
field: 'fieldSwitch',
label: '开关',
component: 'Switch',
},
{
field: 'fieldDivider',
label: '分割线',
component: 'Divider',
componentProps: { content: '分割线' },
},
{
field: 'fieldSelect',
label: '下拉选择',
component: 'Select',
componentProps: {
options: [
{ fullName: '启用', id: 1 },
{ fullName: '锁定', id: 2 },
{ fullName: '禁用', id: 0 },
],
},
},
{
field: 'fieldSelect2',
label: '下拉多选',
component: 'Select',
componentProps: {
multiple: true,
options: [
{ fullName: '启用', id: 1 },
{ fullName: '锁定', id: 2 },
{ fullName: '禁用', id: 0 },
],
},
},
{
field: 'fieldRadio',
label: '单选框组',
component: 'Radio',
componentProps: {
options: [
{ fullName: '启用', id: 1 },
{ fullName: '锁定', id: 2 },
{ fullName: '禁用', id: 0 },
],
},
},
{
field: 'fieldCheckbox',
label: '多选框组',
component: 'Checkbox',
componentProps: {
options: [
{ fullName: '启用', id: 1 },
{ fullName: '锁定', id: 2 },
{ fullName: '禁用', id: 0 },
],
},
},
{
field: 'fieldButton',
label: '按钮',
component: 'Button',
componentProps: { buttonText: '按钮' },
},
{
field: 'fieldTreeSelect',
label: '下拉树形',
component: 'TreeSelect',
},
{
field: 'fieldGroupTitle',
label: '分组标题',
component: 'GroupTitle',
componentProps: { content: '分组标题' },
},
{
field: 'fieldDatePicker',
label: '日期选择',
component: 'DatePicker',
},
{
field: 'fieldTimePicker',
label: '时间选择',
component: 'TimePicker',
},
{
field: 'fieldUploadImg',
label: '图片上传',
component: 'UploadImg',
},
{
field: 'fieldOrganizeSelect',
label: '组织选择',
component: 'OrganizeSelect',
},
{
field: 'fieldDepSelect',
label: '部门选择',
component: 'DepSelect',
},
{
field: 'fieldPosSelect',
label: '岗位选择',
component: 'PosSelect',
},
{
field: 'fieldGroupSelect',
label: '分组选择',
component: 'GroupSelect',
},
{
field: 'fieldRoleSelect',
label: '角色选择',
component: 'RoleSelect',
},
{
field: 'fieldUserSelect',
label: '用户选择',
component: 'UserSelect',
},
{
field: 'fieldUsersSelect',
label: '用户组件',
component: 'UsersSelect',
},
{
field: 'fieldCascader',
label: '级联选择',
component: 'Cascader',
},
{
field: 'fieldColorPicker',
label: '颜色选择',
component: 'ColorPicker',
},
{
field: 'fieldRate',
label: '评分',
component: 'Rate',
},
{
field: 'fieldSlider',
label: '滑块',
component: 'Slider',
},
{
field: 'fieldEditor',
label: '富文本',
component: 'Editor',
},
{
field: 'fieldLink',
label: '链接',
component: 'Link',
componentProps: { content: '官网', href: 'https://yunzhupaas.com' },
},
{
field: 'fieldText',
label: '文本',
component: 'Text',
componentProps: { content: '这是一段文字' },
},
{
field: 'fieldAlert',
label: '提示',
component: 'Alert',
componentProps: { message: '这是一段提示', type: 'success', showIcon: true },
},
{
field: 'fieldAreaSelect',
label: '省市区域',
component: 'AreaSelect',
},
{
field: 'fieldBillRule',
label: '单据规则',
component: 'BillRule',
},
{
field: 'fieldModifyUser',
label: '修改人员',
component: 'ModifyUser',
},
{
field: 'fieldModifyTime',
label: '修改时间',
component: 'ModifyTime',
},
{
field: 'fieldCreateUser',
label: '创建人员',
component: 'CreateUser',
componentProps: { type: 'currUser' },
},
{
field: 'fieldCreateTime',
label: '创建时间',
component: 'CreateTime',
componentProps: { type: 'currTime' },
},
{
field: 'fieldCurrOrganize',
label: '所属组织',
component: 'CurrOrganize',
componentProps: { type: 'currOrganize' },
},
{
field: 'fieldCurrPosition',
label: '所属岗位',
component: 'CurrPosition',
componentProps: { type: 'currPosition' },
},
{
field: 'fieldIconPicker',
label: '图标选择',
component: 'IconPicker',
},
{
field: 'fieldSign',
label: '在线签名',
component: 'Sign',
},
{
field: 'fieldQrcode',
label: '二维码',
component: 'Qrcode',
componentProps: { staticText: '二维码' },
},
{
field: 'fieldBarcode',
label: '条形码',
component: 'Barcode',
componentProps: { staticText: '10241024' },
},
{
field: 'fieldNumberRange',
label: '条形码',
component: 'NumberRange',
componentProps: { staticText: '10241024' },
},
];
return schemas;
};

Some files were not shown because too many files have changed in this diff Show More