初始代码
This commit is contained in:
27
src/views/basic/error-log/DetailModal.vue
Normal file
27
src/views/basic/error-log/DetailModal.vue
Normal 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>
|
||||
67
src/views/basic/error-log/data.tsx
Normal file
67
src/views/basic/error-log/data.tsx
Normal 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,
|
||||
};
|
||||
});
|
||||
}
|
||||
94
src/views/basic/error-log/index.vue
Normal file
94
src/views/basic/error-log/index.vue
Normal 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>
|
||||
93
src/views/basic/exception/Exception.vue
Normal file
93
src/views/basic/exception/Exception.vue
Normal 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>
|
||||
1
src/views/basic/exception/index.ts
Normal file
1
src/views/basic/exception/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Exception } from './Exception.vue';
|
||||
46
src/views/basic/home/Default.vue
Normal file
46
src/views/basic/home/Default.vue
Normal 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>
|
||||
95
src/views/basic/home/Setting.vue
Normal file
95
src/views/basic/home/Setting.vue
Normal 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>
|
||||
86
src/views/basic/home/components/GrowCard.vue
Normal file
86
src/views/basic/home/components/GrowCard.vue
Normal 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>
|
||||
64
src/views/basic/home/components/SalesProductPie.vue
Normal file
64
src/views/basic/home/components/SalesProductPie.vue
Normal 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>
|
||||
33
src/views/basic/home/components/SiteAnalysis.vue
Normal file
33
src/views/basic/home/components/SiteAnalysis.vue
Normal 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>
|
||||
93
src/views/basic/home/components/VisitAnalysis.vue
Normal file
93
src/views/basic/home/components/VisitAnalysis.vue
Normal 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>
|
||||
48
src/views/basic/home/components/VisitAnalysisBar.vue
Normal file
48
src/views/basic/home/components/VisitAnalysisBar.vue
Normal 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>
|
||||
94
src/views/basic/home/components/VisitRadar.vue
Normal file
94
src/views/basic/home/components/VisitRadar.vue
Normal 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>
|
||||
81
src/views/basic/home/components/VisitSource.vue
Normal file
81
src/views/basic/home/components/VisitSource.vue
Normal 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>
|
||||
16
src/views/basic/home/components/props.ts
Normal file
16
src/views/basic/home/components/props.ts
Normal 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',
|
||||
},
|
||||
};
|
||||
67
src/views/basic/home/data.ts
Normal file
67
src/views/basic/home/data.ts
Normal 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: '年',
|
||||
},
|
||||
];
|
||||
155
src/views/basic/home/hooks/usePortal.ts
Normal file
155
src/views/basic/home/hooks/usePortal.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
102
src/views/basic/home/index.vue
Normal file
102
src/views/basic/home/index.vue
Normal 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>
|
||||
9
src/views/basic/iframe/FrameBlank.vue
Normal file
9
src/views/basic/iframe/FrameBlank.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'FrameBlank',
|
||||
});
|
||||
</script>
|
||||
95
src/views/basic/iframe/index copy.vue
Normal file
95
src/views/basic/iframe/index copy.vue
Normal 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>
|
||||
102
src/views/basic/iframe/index.vue
Normal file
102
src/views/basic/iframe/index.vue
Normal 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>
|
||||
213
src/views/basic/lock/LockPage.vue
Normal file
213
src/views/basic/lock/LockPage.vue
Normal 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>
|
||||
13
src/views/basic/lock/index.vue
Normal file
13
src/views/basic/lock/index.vue
Normal 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>
|
||||
60
src/views/basic/lock/useNow.ts
Normal file
60
src/views/basic/lock/useNow.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
48
src/views/basic/login/Login.vue
Normal file
48
src/views/basic/login/Login.vue
Normal 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>
|
||||
358
src/views/basic/login/LoginForm.vue
Normal file
358
src/views/basic/login/LoginForm.vue
Normal 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>
|
||||
17
src/views/basic/login/LoginFormTitle.vue
Normal file
17
src/views/basic/login/LoginFormTitle.vue
Normal 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>
|
||||
113
src/views/basic/login/QrCodeForm.vue
Normal file
113
src/views/basic/login/QrCodeForm.vue
Normal 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>
|
||||
45
src/views/basic/login/SessionTimeoutLogin.vue
Normal file
45
src/views/basic/login/SessionTimeoutLogin.vue
Normal 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>
|
||||
466
src/views/basic/login/index.less
Normal file
466
src/views/basic/login/index.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/views/basic/login/sso-redirect.vue
Normal file
40
src/views/basic/login/sso-redirect.vue
Normal 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>
|
||||
124
src/views/basic/login/useLogin.ts
Normal file
124
src/views/basic/login/useLogin.ts
Normal 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',
|
||||
},
|
||||
];
|
||||
}
|
||||
175
src/views/basic/messageRecord/index.vue
Normal file
175
src/views/basic/messageRecord/index.vue
Normal 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>
|
||||
69
src/views/basic/profile/components/Authorize.vue
Normal file
69
src/views/basic/profile/components/Authorize.vue
Normal 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>
|
||||
189
src/views/basic/profile/components/Entrust/Form.vue
Normal file
189
src/views/basic/profile/components/Entrust/Form.vue
Normal 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>
|
||||
285
src/views/basic/profile/components/Entrust/index.vue
Normal file
285
src/views/basic/profile/components/Entrust/index.vue
Normal 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>
|
||||
108
src/views/basic/profile/components/JustAuth.vue
Normal file
108
src/views/basic/profile/components/JustAuth.vue
Normal 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>
|
||||
155
src/views/basic/profile/components/Password.vue
Normal file
155
src/views/basic/profile/components/Password.vue
Normal 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>
|
||||
175
src/views/basic/profile/components/SysLog.vue
Normal file
175
src/views/basic/profile/components/SysLog.vue
Normal 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>
|
||||
92
src/views/basic/profile/components/TenantInfo.vue
Normal file
92
src/views/basic/profile/components/TenantInfo.vue
Normal 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>
|
||||
481
src/views/basic/profile/components/UserInfo.vue
Normal file
481
src/views/basic/profile/components/UserInfo.vue
Normal 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>
|
||||
453
src/views/basic/profile/index.vue
Normal file
453
src/views/basic/profile/index.vue
Normal 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>
|
||||
30
src/views/basic/redirect/index.vue
Normal file
30
src/views/basic/redirect/index.vue
Normal 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>
|
||||
61
src/views/common/dynamicDataReport/index.vue
Normal file
61
src/views/common/dynamicDataReport/index.vue
Normal 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>
|
||||
127
src/views/common/dynamicDictionary/Form.vue
Normal file
127
src/views/common/dynamicDictionary/Form.vue
Normal 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>
|
||||
116
src/views/common/dynamicDictionary/index.vue
Normal file
116
src/views/common/dynamicDictionary/index.vue
Normal 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>
|
||||
144
src/views/common/dynamicModel/form/FormPopup.vue
Normal file
144
src/views/common/dynamicModel/form/FormPopup.vue
Normal 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>
|
||||
41
src/views/common/dynamicModel/form/index.vue
Normal file
41
src/views/common/dynamicModel/form/index.vue
Normal 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>
|
||||
97
src/views/common/dynamicModel/index.vue
Normal file
97
src/views/common/dynamicModel/index.vue
Normal 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>
|
||||
142
src/views/common/dynamicModel/list/ChildTableColumn.vue
Normal file
142
src/views/common/dynamicModel/list/ChildTableColumn.vue
Normal 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>
|
||||
244
src/views/common/dynamicModel/list/CustomForm.vue
Normal file
244
src/views/common/dynamicModel/list/CustomForm.vue
Normal 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>
|
||||
395
src/views/common/dynamicModel/list/Form.vue
Normal file
395
src/views/common/dynamicModel/list/Form.vue
Normal 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>
|
||||
128
src/views/common/dynamicModel/list/components/ViewList.vue
Normal file
128
src/views/common/dynamicModel/list/components/ViewList.vue
Normal 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>
|
||||
213
src/views/common/dynamicModel/list/components/ViewSetting.vue
Normal file
213
src/views/common/dynamicModel/list/components/ViewSetting.vue
Normal 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>
|
||||
669
src/views/common/dynamicModel/list/detail/ExtraList.vue
Normal file
669
src/views/common/dynamicModel/list/detail/ExtraList.vue
Normal 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>
|
||||
716
src/views/common/dynamicModel/list/detail/Item.vue
Normal file
716
src/views/common/dynamicModel/list/detail/Item.vue
Normal 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>
|
||||
40
src/views/common/dynamicModel/list/detail/Parser.vue
Normal file
40
src/views/common/dynamicModel/list/detail/Parser.vue
Normal 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>
|
||||
445
src/views/common/dynamicModel/list/detail/index.vue
Normal file
445
src/views/common/dynamicModel/list/detail/index.vue
Normal 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>
|
||||
2006
src/views/common/dynamicModel/list/index.vue
Normal file
2006
src/views/common/dynamicModel/list/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
42
src/views/common/dynamicPortal/index.vue
Normal file
42
src/views/common/dynamicPortal/index.vue
Normal 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>
|
||||
196
src/views/common/dynamicReport/index.vue
Normal file
196
src/views/common/dynamicReport/index.vue
Normal 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>
|
||||
25
src/views/common/externalLink/index.vue
Normal file
25
src/views/common/externalLink/index.vue
Normal 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>
|
||||
217
src/views/common/formShortLink/form/index.vue
Normal file
217
src/views/common/formShortLink/form/index.vue
Normal 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>
|
||||
66
src/views/common/formShortLink/index.vue
Normal file
66
src/views/common/formShortLink/index.vue
Normal 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>
|
||||
167
src/views/common/formShortLink/list/detail/index.vue
Normal file
167
src/views/common/formShortLink/list/detail/index.vue
Normal 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>
|
||||
675
src/views/common/formShortLink/list/index.vue
Normal file
675
src/views/common/formShortLink/list/index.vue
Normal 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>
|
||||
225
src/views/crm/crmlead/Detail.vue
Normal file
225
src/views/crm/crmlead/Detail.vue
Normal 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>
|
||||
457
src/views/crm/crmlead/Form.vue
Normal file
457
src/views/crm/crmlead/Form.vue
Normal 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>
|
||||
34
src/views/crm/crmlead/helper/api.ts
Normal file
34
src/views/crm/crmlead/helper/api.ts
Normal 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 });
|
||||
}
|
||||
503
src/views/crm/crmlead/helper/columnList.ts
Normal file
503
src/views/crm/crmlead/helper/columnList.ts
Normal 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
|
||||
146
src/views/crm/crmlead/helper/searchList.ts
Normal file
146
src/views/crm/crmlead/helper/searchList.ts
Normal 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
|
||||
440
src/views/crm/crmlead/helper/superQueryJson.ts
Normal file
440
src/views/crm/crmlead/helper/superQueryJson.ts
Normal 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
|
||||
686
src/views/crm/crmlead/index.vue
Normal file
686
src/views/crm/crmlead/index.vue
Normal 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>
|
||||
295
src/views/crm/crmopportunity/Detail.vue
Normal file
295
src/views/crm/crmopportunity/Detail.vue
Normal 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>
|
||||
647
src/views/crm/crmopportunity/Form.vue
Normal file
647
src/views/crm/crmopportunity/Form.vue
Normal 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>
|
||||
34
src/views/crm/crmopportunity/helper/api.ts
Normal file
34
src/views/crm/crmopportunity/helper/api.ts
Normal 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 });
|
||||
}
|
||||
619
src/views/crm/crmopportunity/helper/columnList.ts
Normal file
619
src/views/crm/crmopportunity/helper/columnList.ts
Normal 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
|
||||
205
src/views/crm/crmopportunity/helper/searchList.ts
Normal file
205
src/views/crm/crmopportunity/helper/searchList.ts
Normal 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
|
||||
738
src/views/crm/crmopportunity/helper/superQueryJson.ts
Normal file
738
src/views/crm/crmopportunity/helper/superQueryJson.ts
Normal 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
|
||||
652
src/views/crm/crmopportunity/index.vue
Normal file
652
src/views/crm/crmopportunity/index.vue
Normal 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>
|
||||
1052
src/views/crm/mdmcompany/Detail.vue
Normal file
1052
src/views/crm/mdmcompany/Detail.vue
Normal file
File diff suppressed because it is too large
Load Diff
1914
src/views/crm/mdmcompany/Form.vue
Normal file
1914
src/views/crm/mdmcompany/Form.vue
Normal file
File diff suppressed because it is too large
Load Diff
34
src/views/crm/mdmcompany/helper/api.ts
Normal file
34
src/views/crm/mdmcompany/helper/api.ts
Normal 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 });
|
||||
}
|
||||
784
src/views/crm/mdmcompany/helper/columnList.ts
Normal file
784
src/views/crm/mdmcompany/helper/columnList.ts
Normal 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
|
||||
149
src/views/crm/mdmcompany/helper/searchList.ts
Normal file
149
src/views/crm/mdmcompany/helper/searchList.ts
Normal 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
|
||||
2275
src/views/crm/mdmcompany/helper/superQueryJson.ts
Normal file
2275
src/views/crm/mdmcompany/helper/superQueryJson.ts
Normal file
File diff suppressed because it is too large
Load Diff
652
src/views/crm/mdmcompany/index.vue
Normal file
652
src/views/crm/mdmcompany/index.vue
Normal 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>
|
||||
96
src/views/extend/barCode/index.vue
Normal file
96
src/views/extend/barCode/index.vue
Normal 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>
|
||||
79
src/views/extend/bigData/index.vue
Normal file
79
src/views/extend/bigData/index.vue
Normal 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>
|
||||
97
src/views/extend/documentPreview/Preview.vue
Normal file
97
src/views/extend/documentPreview/Preview.vue
Normal 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>
|
||||
103
src/views/extend/documentPreview/index.vue
Normal file
103
src/views/extend/documentPreview/index.vue
Normal 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>
|
||||
129
src/views/extend/email/Config.vue
Normal file
129
src/views/extend/email/Config.vue
Normal 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>
|
||||
35
src/views/extend/email/Detail.vue
Normal file
35
src/views/extend/email/Detail.vue
Normal 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>
|
||||
73
src/views/extend/email/DetailMain.vue
Normal file
73
src/views/extend/email/DetailMain.vue
Normal 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 + '<' + 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>
|
||||
45
src/views/extend/email/DetailPage.vue
Normal file
45
src/views/extend/email/DetailPage.vue
Normal 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>
|
||||
151
src/views/extend/email/Form.vue
Normal file
151
src/views/extend/email/Form.vue
Normal 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>
|
||||
-
|
||||
<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>
|
||||
325
src/views/extend/email/index.vue
Normal file
325
src/views/extend/email/index.vue
Normal 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>
|
||||
100
src/views/extend/formDemo/fieldForm1/index.vue
Normal file
100
src/views/extend/formDemo/fieldForm1/index.vue
Normal 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>
|
||||
60
src/views/extend/formDemo/fieldForm1/index1.vue
Normal file
60
src/views/extend/formDemo/fieldForm1/index1.vue
Normal 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>
|
||||
275
src/views/extend/formDemo/fieldForm1/schemaData.tsx
Normal file
275
src/views/extend/formDemo/fieldForm1/schemaData.tsx
Normal 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
Reference in New Issue
Block a user