Commit 76e5628f93e882e66f23e50ed00d057dd00a5587

Authored by xp.Huang
2 parents 674be7d5 70954e52

Merge branch 'sqy_dev' into 'main'

fix:首页开发,OEM定制开发

See merge request huang/yun-teng-iot-front!13
Showing 34 changed files with 2165 additions and 534 deletions
@@ -15,7 +15,7 @@ enum API { @@ -15,7 +15,7 @@ enum API {
15 15
16 // 获取 16 // 获取
17 export const getAlarmContact = (params: ContactPageParams) => { 17 export const getAlarmContact = (params: ContactPageParams) => {
18 - return getPageData<ContactModal>(params, Api.alarmContact); 18 + return getPageData<ContactModal>(params, API.alarmContact);
19 }; 19 };
20 20
21 // 新增 21 // 新增
@@ -48,6 +48,7 @@ export const saveOrEditAlarmContact = (params: ContactInfo, isUpdate: boolean) = @@ -48,6 +48,7 @@ export const saveOrEditAlarmContact = (params: ContactInfo, isUpdate: boolean) =
48 addAlarmContact(params); 48 addAlarmContact(params);
49 }; 49 };
50 50
  51 +// 查询设备分页数据
51 export const devicePage = (params: DeviceQueryParam) => { 52 export const devicePage = (params: DeviceQueryParam) => {
52 return defHttp.get<DeviceModel>({ 53 return defHttp.get<DeviceModel>({
53 url: API.devicePage, 54 url: API.devicePage,
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { FileUploadResponse } from './model/index';
  3 +enum API {
  4 + SELECT_DETAIL = '/enterprise/get',
  5 + UPDATE_DETAIL = '/enterprise/update',
  6 + TOWN_LIST = '/town/list',
  7 + TOWN_CHILDS = '/town/childs',
  8 + BaseUploadUrl = '/oss/upload',
  9 + SELECT_PLATFORM = '/platform/get',
  10 + UPDATE_PLATFORM = '/platform/update',
  11 +
  12 + SELECT_APP_DESIGN = '/appDesign/get',
  13 + UPDATE_APP_DESIGN = '/appDesign/update',
  14 +}
  15 +
  16 +// 查询企业信息
  17 +export const getEnterPriseDetail = () => {
  18 + return defHttp.get({
  19 + url: API.SELECT_DETAIL,
  20 + });
  21 +};
  22 +
  23 +// 更新企业信息
  24 +export const updateEnterPriseDetail = (data) => {
  25 + return defHttp.put({
  26 + url: API.UPDATE_DETAIL,
  27 + data,
  28 + });
  29 +};
  30 +
  31 +// 获取所有省份
  32 +export const getTownList = () => {
  33 + return defHttp.get({
  34 + url: API.TOWN_LIST,
  35 + });
  36 +};
  37 +
  38 +// 获取省份下面的地址
  39 +export const getTownChild = (key, value) => {
  40 + return defHttp.get({
  41 + url: API.TOWN_CHILDS + `/${key}/${value}`,
  42 + params: {
  43 + variable: key,
  44 + value,
  45 + },
  46 + });
  47 +};
  48 +
  49 +// logo上传
  50 +export const logoUpload = (file) => {
  51 + return defHttp.post<FileUploadResponse>({ url: API.BaseUploadUrl, params: file });
  52 +};
  53 +
  54 +// icon上传
  55 +export const iconUpload = (file) => {
  56 + return defHttp.post<FileUploadResponse>({ url: API.BaseUploadUrl, params: file });
  57 +};
  58 +
  59 +// 背景图片上传
  60 +export const bgUpload = (file) => {
  61 + return defHttp.post<FileUploadResponse>({ url: API.BaseUploadUrl, params: file });
  62 +};
  63 +
  64 +// 获取平台定制详情
  65 +export const getPlatForm = () => {
  66 + return defHttp.get({
  67 + url: API.SELECT_PLATFORM,
  68 + });
  69 +};
  70 +
  71 +// 更新平台定制
  72 +export const updatePlatForm = (data) => {
  73 + return defHttp.put({
  74 + url: API.UPDATE_PLATFORM,
  75 + data,
  76 + });
  77 +};
  78 +
  79 +// APP定制
  80 +export const getAppDesign = () => {
  81 + return defHttp.get({
  82 + url: API.SELECT_APP_DESIGN,
  83 + });
  84 +};
  85 +
  86 +// 更新APP定制
  87 +export const updateAppDesign = (data) => {
  88 + return defHttp.put({
  89 + url: API.UPDATE_APP_DESIGN,
  90 + data,
  91 + });
  92 +};
  1 +export interface FileUploadResponse {
  2 + fileName: string;
  3 + fileDownloadUri: string;
  4 + fileType: string;
  5 + size: number;
  6 + fileStaticUri: string;
  7 +}
@@ -196,8 +196,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) { @@ -196,8 +196,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
196 export const defHttp = createAxios(); 196 export const defHttp = createAxios();
197 197
198 // other api url 198 // other api url
199 -// export const otherHttp = createAxios({  
200 -// requestOptions: {  
201 -// apiUrl: 'xxx',  
202 -// },  
203 -// }); 199 +export const otherHttp = createAxios({
  200 + requestOptions: {
  201 + apiUrl: 'xxx',
  202 + },
  203 +});
@@ -33,6 +33,7 @@ @@ -33,6 +33,7 @@
33 'https://api.map.baidu.com/getscript?v=3.0&ak=7uOPPyAHn2Y2ZryeQqHtcRqtIY374vKa'; 33 'https://api.map.baidu.com/getscript?v=3.0&ak=7uOPPyAHn2Y2ZryeQqHtcRqtIY374vKa';
34 const wrapRef = ref<HTMLDivElement | null>(null); 34 const wrapRef = ref<HTMLDivElement | null>(null);
35 const { toPromise } = useScript({ src: BAI_DU_MAP_URL }); 35 const { toPromise } = useScript({ src: BAI_DU_MAP_URL });
  36 +
36 async function initMap() { 37 async function initMap() {
37 await toPromise(); 38 await toPromise();
38 await nextTick(); 39 await nextTick();
1 <template> 1 <template>
2 <div class="md:flex"> 2 <div class="md:flex">
3 <template v-for="(item, index) in growCardList" :key="item.title"> 3 <template v-for="(item, index) in growCardList" :key="item.title">
4 - <Card  
5 - size="small"  
6 - :loading="$attrs.loading"  
7 - :title="item.title"  
8 - class="md:w-1/4 w-full !md:mt-0 !mt-4"  
9 - :class="[index + 1 < 4 && '!md:mr-4']"  
10 - :canExpan="false" 4 + <div
  5 + class="growCardItem md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4"
  6 + :class="[index + 1 < 3 && '!md:mr-4']"
11 > 7 >
12 - <template #extra>  
13 - <Tag :color="item.color">{{ item.action }}</Tag>  
14 - </template>  
15 -  
16 - <div class="py-4 px-4 flex justify-between">  
17 - <CountTo prefix="$" :startVal="1" :endVal="item.value" class="text-2xl" />  
18 - <Icon :icon="item.icon" :size="40" />  
19 - </div>  
20 -  
21 - <div class="p-2 px-4 flex justify-between">  
22 - <span>总{{ item.title }}</span>  
23 - <CountTo prefix="$" :startVal="1" :endVal="item.total" /> 8 + <div class="growCardItem-top">
  9 + <img :src="item.imgUrl" style="width: 90px; height: 90px" />
  10 + <div class="growCardItem-right">
  11 + <div class="flex justify-between ml-3">
  12 + <div style="font-size: 26px; color: #333">{{ item.value }}</div>
  13 + <img src="../../../../assets/images/tip.png" style="width: 20px; height: 20px" />
  14 + </div>
  15 + <div class="ml-3">{{ item.title }}</div>
  16 + <div class="ml-1.5 mt-3 flex flex-nowrap" style="width: 240px" v-if="item.offLine">
  17 + <div class="count">
  18 + <img
  19 + src="../../../../assets/images/online.png"
  20 + style="width: 10px; height: 10px; margin-right: 4px"
  21 + />
  22 + 在线 {{ item.onLine }}
  23 + </div>
  24 + <div class="count">
  25 + <img
  26 + src="../../../../assets/images/offline.png"
  27 + style="width: 10px; height: 10px; margin-right: 4px"
  28 + />
  29 + 离线 {{ item.offLine }}
  30 + </div>
  31 + <div class="count">
  32 + <img
  33 + src="../../../../assets/images/inactive.png"
  34 + style="width: 10px; height: 10px; margin-right: 4px"
  35 + />
  36 + 未激活 {{ item.inactive }}
  37 + </div>
  38 + </div>
  39 + </div>
24 </div> 40 </div>
25 - </Card> 41 + <div class="growCardItem-bottom"> 今日新增 {{ item.newDay }}</div>
  42 + </div>
26 </template> 43 </template>
27 </div> 44 </div>
28 </template> 45 </template>
29 <script lang="ts" setup> 46 <script lang="ts" setup>
30 - import { CountTo } from '/@/components/CountTo/index';  
31 - import { Icon } from '/@/components/Icon';  
32 - import { Tag, Card } from 'ant-design-vue';  
33 import { growCardList } from '../data'; 47 import { growCardList } from '../data';
34 </script> 48 </script>
  49 +
  50 +<style scoped lang="less">
  51 + .growCardItem {
  52 + height: 179px;
  53 + background-color: #fff;
  54 + color: #666;
  55 + .growCardItem-top {
  56 + display: flex;
  57 + margin: 20px;
  58 + border-bottom: 1px solid #f2f2f5;
  59 + padding-bottom: 10px;
  60 + .growCardItem-right {
  61 + width: 300px;
  62 + .count {
  63 + display: flex;
  64 + font-size: 12px;
  65 + align-items: center;
  66 + margin-left: 8px;
  67 + }
  68 + }
  69 + }
  70 + .growCardItem-bottom {
  71 + margin-left: 20px;
  72 + }
  73 + }
  74 +</style>
  1 +<template>
  2 + <Card title="帮助文档">
  3 + <div>
  4 + <template v-for="item in helpDoc" :key="item.title">
  5 + <AnchorLink v-bind="item" />
  6 + </template>
  7 + </div>
  8 + <Card
  9 + :tab-list="tabListTitle"
  10 + v-bind="$attrs"
  11 + :active-tab-key="activeKey"
  12 + :bordered="false"
  13 + @tabChange="onTabChange"
  14 + :bodyStyle="{ padding: 0 }"
  15 + >
  16 + <div v-if="activeKey === 'tab1'">
  17 + <List item-layout="horizontal" :data-source="data">
  18 + <template #renderItem="{ item }">
  19 + <ListItem>
  20 + <ListItemMeta :description="item.description">
  21 + <template #title>
  22 + <a href="https://www.antdv.com/">{{ item.title }}</a>
  23 + </template>
  24 + <template #avatar>
  25 + <Avatar :src="item.avatar" />
  26 + </template>
  27 + </ListItemMeta>
  28 + <template #extra> {{ item.date }} </template>
  29 + </ListItem>
  30 + </template>
  31 + </List>
  32 + </div>
  33 + <div v-else>222</div>
  34 + </Card>
  35 + <Card hoverable title="联系我们" :bordered="false">
  36 + <template #cover>
  37 + <QrCode :value="qrCodeUrl" class="flex justify-center" />
  38 + </template>
  39 + <CardMeta>
  40 + <template #description>
  41 + <p>联系人: 张三</p>
  42 + <p>联系电话: 15912341234</p>
  43 + <p>联系地址: 四川省成都市剑南大道北段中1533号 </p>
  44 + </template>
  45 + </CardMeta>
  46 + </Card>
  47 + </Card>
  48 +</template>
  49 +
  50 +<script lang="ts">
  51 + import { defineComponent, ref } from 'vue';
  52 + import { Card, AnchorLink, List, ListItem, ListItemMeta, Avatar, CardMeta } from 'ant-design-vue';
  53 + import { QrCode } from '/@/components/Qrcode/index';
  54 + export default defineComponent({
  55 + components: {
  56 + Card,
  57 + AnchorLink,
  58 + List,
  59 + ListItem,
  60 + ListItemMeta,
  61 + Avatar,
  62 + CardMeta,
  63 + QrCode,
  64 + },
  65 + setup() {
  66 + const helpDoc = ref([
  67 + {
  68 + title: '如何接入设备?',
  69 + href: '',
  70 + },
  71 + {
  72 + title: '什么是设备配置?',
  73 + href: '',
  74 + },
  75 + {
  76 + title: '云组态模板如何使用?',
  77 + href: '',
  78 + },
  79 + {
  80 + title: '查看全部>>',
  81 + href: '',
  82 + },
  83 + ]);
  84 +
  85 + const activeKey = ref('tab1');
  86 + const tabListTitle = [
  87 + {
  88 + key: 'tab1',
  89 + tab: '通知公告',
  90 + },
  91 + {
  92 + key: 'tab2',
  93 + tab: '系统公告',
  94 + },
  95 + ];
  96 + const onTabChange = (key) => {
  97 + activeKey.value = key;
  98 + };
  99 +
  100 + // 列表
  101 + interface DataItem {
  102 + title: string;
  103 + description: string;
  104 + avatar: string;
  105 + date: string;
  106 + }
  107 + const data: DataItem[] = [
  108 + {
  109 + title: '企业管理员',
  110 + description: '现在就来开创新的记录吧!',
  111 + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  112 + date: '5分钟前',
  113 + },
  114 + {
  115 + title: '企业管理员',
  116 + description: '有新的告警数据需要处理,现在去查看吧',
  117 + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  118 + date: '7小时前',
  119 + },
  120 + {
  121 + title: '管理员',
  122 + description: '有新的告警数据需要处理,现在去查看吧',
  123 + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  124 + date: '6小时前',
  125 + },
  126 + {
  127 + title: '管理员',
  128 + description: '现在就来开创新的记录吧!',
  129 + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  130 + date: '1小时前',
  131 + },
  132 + {
  133 + title: '管理员',
  134 + description: '现在就来开创新的记录吧!',
  135 + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  136 + date: '7小时前',
  137 + },
  138 + ];
  139 +
  140 + const qrCodeUrl = 'https://www.vvbin.cn';
  141 +
  142 + return {
  143 + activeKey,
  144 + tabListTitle,
  145 + onTabChange,
  146 + data,
  147 + helpDoc,
  148 + qrCodeUrl,
  149 + };
  150 + },
  151 + });
  152 +</script>
  153 +
  154 +<style lang="less" scoped></style>
1 -<template>  
2 - <Card title="成交占比" :loading="loading">  
3 - <div ref="chartRef" :style="{ width, height }"></div>  
4 - </Card>  
5 -</template>  
6 -<script lang="ts" setup>  
7 - import { Ref, ref, watch } from 'vue';  
8 - import { Card } from 'ant-design-vue';  
9 - import { useECharts } from '/@/hooks/web/useECharts';  
10 -  
11 - const props = defineProps({  
12 - loading: Boolean,  
13 - width: {  
14 - type: String as PropType<string>,  
15 - default: '100%',  
16 - },  
17 - height: {  
18 - type: String as PropType<string>,  
19 - default: '300px',  
20 - },  
21 - });  
22 -  
23 - const chartRef = ref<HTMLDivElement | null>(null);  
24 - const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);  
25 - watch(  
26 - () => props.loading,  
27 - () => {  
28 - if (props.loading) {  
29 - return;  
30 - }  
31 - setOptions({  
32 - tooltip: {  
33 - trigger: 'item',  
34 - },  
35 -  
36 - series: [  
37 - {  
38 - name: '访问来源',  
39 - type: 'pie',  
40 - radius: '80%',  
41 - center: ['50%', '50%'],  
42 - color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],  
43 - data: [  
44 - { value: 500, name: '电子产品' },  
45 - { value: 310, name: '服装' },  
46 - { value: 274, name: '化妆品' },  
47 - { value: 400, name: '家居' },  
48 - ].sort(function (a, b) {  
49 - return a.value - b.value;  
50 - }),  
51 - roseType: 'radius',  
52 - animationType: 'scale',  
53 - animationEasing: 'exponentialInOut',  
54 - animationDelay: function () {  
55 - return Math.random() * 400;  
56 - },  
57 - },  
58 - ],  
59 - });  
60 - },  
61 - { immediate: true }  
62 - );  
63 -</script>  
@@ -5,17 +5,29 @@ @@ -5,17 +5,29 @@
5 :active-tab-key="activeKey" 5 :active-tab-key="activeKey"
6 @tabChange="onTabChange" 6 @tabChange="onTabChange"
7 > 7 >
8 - <p v-if="activeKey === 'tab1'"> 8 + <template #tabBarExtraContent>
  9 + <div class="extra-date">
  10 + <template v-for="(item, index) in dateList" :key="item">
  11 + <span @click="changeDate(index)" :class="{ active: index === activeIndex }">{{
  12 + item
  13 + }}</span>
  14 + </template>
  15 + <DatePicker @change="onDateChange" />
  16 + </div>
  17 + </template>
  18 + <div v-if="activeKey === 'tab1'">
  19 + <p class="center">告警数</p>
9 <VisitAnalysis /> 20 <VisitAnalysis />
10 - </p>  
11 - <p v-if="activeKey === 'tab2'"> 21 + </div>
  22 + <div v-else>
  23 + <p class="center">消息数</p>
12 <VisitAnalysisBar /> 24 <VisitAnalysisBar />
13 - </p> 25 + </div>
14 </Card> 26 </Card>
15 </template> 27 </template>
16 <script lang="ts" setup> 28 <script lang="ts" setup>
17 import { ref } from 'vue'; 29 import { ref } from 'vue';
18 - import { Card } from 'ant-design-vue'; 30 + import { Card, DatePicker } from 'ant-design-vue';
19 import VisitAnalysis from './VisitAnalysis.vue'; 31 import VisitAnalysis from './VisitAnalysis.vue';
20 import VisitAnalysisBar from './VisitAnalysisBar.vue'; 32 import VisitAnalysisBar from './VisitAnalysisBar.vue';
21 33
@@ -24,15 +36,43 @@ @@ -24,15 +36,43 @@
24 const tabListTitle = [ 36 const tabListTitle = [
25 { 37 {
26 key: 'tab1', 38 key: 'tab1',
27 - tab: '流量趋势', 39 + tab: '告警数统计',
28 }, 40 },
29 { 41 {
30 key: 'tab2', 42 key: 'tab2',
31 - tab: '访问量', 43 + tab: '消息量统计',
32 }, 44 },
33 ]; 45 ];
34 - 46 + const dateList = ref(['1小时', '1天', '7天', '30天']);
  47 + const activeIndex = ref(0);
35 function onTabChange(key) { 48 function onTabChange(key) {
36 activeKey.value = key; 49 activeKey.value = key;
37 } 50 }
  51 + function onDateChange(date, dateString) {
  52 + console.log(date, dateString);
  53 + }
  54 + function changeDate(index: number) {
  55 + activeIndex.value = index;
  56 + }
38 </script> 57 </script>
  58 +
  59 +<style scoped lang="less">
  60 + .center {
  61 + display: flex;
  62 + justify-content: center;
  63 + font-size: 16px;
  64 + }
  65 + .active {
  66 + color: #0960bd;
  67 + font-weight: 500;
  68 + }
  69 + .extra-date {
  70 + display: flex;
  71 + align-items: center;
  72 + justify-content: space-between;
  73 + span {
  74 + margin-right: 20px;
  75 + cursor: pointer;
  76 + }
  77 + }
  78 +</style>
@@ -88,18 +88,6 @@ @@ -88,18 +88,6 @@
88 color: '#5ab1ef', 88 color: '#5ab1ef',
89 }, 89 },
90 }, 90 },
91 - {  
92 - smooth: true,  
93 - data: [  
94 - 33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390,  
95 - 198, 60, 30, 22, 11,  
96 - ],  
97 - type: 'line',  
98 - areaStyle: {},  
99 - itemStyle: {  
100 - color: '#019680',  
101 - },  
102 - },  
103 ], 91 ],
104 }); 92 });
105 }); 93 });
1 -<template>  
2 - <Card title="转化率" :loading="loading">  
3 - <div ref="chartRef" :style="{ width, height }"></div>  
4 - </Card>  
5 -</template>  
6 -<script lang="ts" setup>  
7 - import { Ref, ref, watch } from 'vue';  
8 - import { Card } from 'ant-design-vue';  
9 - import { useECharts } from '/@/hooks/web/useECharts';  
10 -  
11 - const props = defineProps({  
12 - loading: Boolean,  
13 - width: {  
14 - type: String as PropType<string>,  
15 - default: '100%',  
16 - },  
17 - height: {  
18 - type: String as PropType<string>,  
19 - default: '300px',  
20 - },  
21 - });  
22 -  
23 - const chartRef = ref<HTMLDivElement | null>(null);  
24 - const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);  
25 - watch(  
26 - () => props.loading,  
27 - () => {  
28 - if (props.loading) {  
29 - return;  
30 - }  
31 - setOptions({  
32 - legend: {  
33 - bottom: 0,  
34 - data: ['访问', '购买'],  
35 - },  
36 - tooltip: {},  
37 - radar: {  
38 - radius: '60%',  
39 - splitNumber: 8,  
40 - indicator: [  
41 - {  
42 - text: '电脑',  
43 - max: 100,  
44 - },  
45 - {  
46 - text: '充电器',  
47 - max: 100,  
48 - },  
49 - {  
50 - text: '耳机',  
51 - max: 100,  
52 - },  
53 - {  
54 - text: '手机',  
55 - max: 100,  
56 - },  
57 - {  
58 - text: 'Ipad',  
59 - max: 100,  
60 - },  
61 - {  
62 - text: '耳机',  
63 - max: 100,  
64 - },  
65 - ],  
66 - },  
67 - series: [  
68 - {  
69 - type: 'radar',  
70 - symbolSize: 0,  
71 - areaStyle: {  
72 - shadowBlur: 0,  
73 - shadowColor: 'rgba(0,0,0,.2)',  
74 - shadowOffsetX: 0,  
75 - shadowOffsetY: 10,  
76 - opacity: 1,  
77 - },  
78 - data: [  
79 - {  
80 - value: [90, 50, 86, 40, 50, 20],  
81 - name: '访问',  
82 - itemStyle: {  
83 - color: '#b6a2de',  
84 - },  
85 - },  
86 - {  
87 - value: [70, 75, 70, 76, 20, 85],  
88 - name: '购买',  
89 - itemStyle: {  
90 - color: '#5ab1ef',  
91 - },  
92 - },  
93 - ],  
94 - },  
95 - ],  
96 - });  
97 - },  
98 - { immediate: true }  
99 - );  
100 -</script>  
1 -<template>  
2 - <Card title="访问来源" :loading="loading">  
3 - <div ref="chartRef" :style="{ width, height }"></div>  
4 - </Card>  
5 -</template>  
6 -<script lang="ts" setup>  
7 - import { Ref, ref, watch } from 'vue';  
8 - import { Card } from 'ant-design-vue';  
9 - import { useECharts } from '/@/hooks/web/useECharts';  
10 - const props = defineProps({  
11 - loading: Boolean,  
12 - width: {  
13 - type: String as PropType<string>,  
14 - default: '100%',  
15 - },  
16 - height: {  
17 - type: String as PropType<string>,  
18 - default: '300px',  
19 - },  
20 - });  
21 - const chartRef = ref<HTMLDivElement | null>(null);  
22 - const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);  
23 - watch(  
24 - () => props.loading,  
25 - () => {  
26 - if (props.loading) {  
27 - return;  
28 - }  
29 - setOptions({  
30 - tooltip: {  
31 - trigger: 'item',  
32 - },  
33 - legend: {  
34 - bottom: '1%',  
35 - left: 'center',  
36 - },  
37 - series: [  
38 - {  
39 - color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],  
40 - name: '访问来源',  
41 - type: 'pie',  
42 - radius: ['40%', '70%'],  
43 - avoidLabelOverlap: false,  
44 - itemStyle: {  
45 - borderRadius: 10,  
46 - borderColor: '#fff',  
47 - borderWidth: 2,  
48 - },  
49 - label: {  
50 - show: false,  
51 - position: 'center',  
52 - },  
53 - emphasis: {  
54 - label: {  
55 - show: true,  
56 - fontSize: '12',  
57 - fontWeight: 'bold',  
58 - },  
59 - },  
60 - labelLine: {  
61 - show: false,  
62 - },  
63 - data: [  
64 - { value: 1048, name: '搜索引擎' },  
65 - { value: 735, name: '直接访问' },  
66 - { value: 580, name: '邮件营销' },  
67 - { value: 484, name: '联盟广告' },  
68 - ],  
69 - animationType: 'scale',  
70 - animationEasing: 'exponentialInOut',  
71 - animationDelay: function () {  
72 - return Math.random() * 100;  
73 - },  
74 - },  
75 - ],  
76 - });  
77 - },  
78 - { immediate: true }  
79 - );  
80 -</script>  
1 export interface GrowCardItem { 1 export interface GrowCardItem {
2 - icon: string; 2 + imgUrl: string;
3 title: string; 3 title: string;
4 - value: number;  
5 - total: number;  
6 - color: string;  
7 - action: string; 4 + value: string;
  5 + onLine?: number;
  6 + offLine?: number;
  7 + inactive?: number;
  8 + newDay: string;
8 } 9 }
9 10
10 export const growCardList: GrowCardItem[] = [ 11 export const growCardList: GrowCardItem[] = [
11 { 12 {
12 - title: '访问数',  
13 - icon: 'visit-count|svg',  
14 - value: 2000,  
15 - total: 120000,  
16 - color: 'green',  
17 - action: '月', 13 + imgUrl: '/src/assets/images/device-count.png',
  14 + title: '设备数(个)',
  15 + value: '10,000',
  16 + onLine: 2000,
  17 + offLine: 3000,
  18 + inactive: 4000,
  19 + newDay: '123,45',
18 }, 20 },
19 { 21 {
20 - title: '成交额',  
21 - icon: 'total-sales|svg',  
22 - value: 20000,  
23 - total: 500000,  
24 - color: 'blue',  
25 - action: '月', 22 + imgUrl: '/src/assets/images/alarm-count.png',
  23 + title: '11月告警数(条)',
  24 + value: '11,000',
  25 + newDay: '167,45',
26 }, 26 },
27 { 27 {
28 - title: '下载数',  
29 - icon: 'download-count|svg',  
30 - value: 8000,  
31 - total: 120000,  
32 - color: 'orange',  
33 - action: '周',  
34 - },  
35 - {  
36 - title: '成交数',  
37 - icon: 'transaction|svg',  
38 - value: 5000,  
39 - total: 50000,  
40 - color: 'purple',  
41 - action: '年', 28 + imgUrl: '/src/assets/images/msg-count.png',
  29 + title: '11月消息量(条)',
  30 + value: '12,000',
  31 + newDay: '198,45',
42 }, 32 },
43 ]; 33 ];
1 <template> 1 <template>
2 - <div class="p-4">  
3 - <GrowCard :loading="loading" class="enter-y" />  
4 - <SiteAnalysis class="!my-4 enter-y" :loading="loading" />  
5 - <div class="md:flex enter-y">  
6 - <VisitRadar class="md:w-1/3 w-full" :loading="loading" />  
7 - <VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />  
8 - <SalesProductPie class="md:w-1/3 w-full" :loading="loading" /> 2 + <div class="p-4 md:flex">
  3 + <div class="md:w-7/10 w-full !mr-4 enter-y">
  4 + <GrowCard :loading="loading" class="enter-y" />
  5 + <SiteAnalysis class="!my-4 enter-y" :loading="loading" />
  6 + <div class="md:flex enter-y">
  7 + <Card title="核心流程指南" style="width: 100%">
  8 + <img alt="核心流程指南" src="../../../assets/images/flow.png" />
  9 + </Card>
  10 + </div>
  11 + </div>
  12 + <div class="md:w-3/10 w-full enter-y">
  13 + <HelpDoc />
9 </div> 14 </div>
10 </div> 15 </div>
11 </template> 16 </template>
@@ -13,12 +18,9 @@ @@ -13,12 +18,9 @@
13 import { ref } from 'vue'; 18 import { ref } from 'vue';
14 import GrowCard from './components/GrowCard.vue'; 19 import GrowCard from './components/GrowCard.vue';
15 import SiteAnalysis from './components/SiteAnalysis.vue'; 20 import SiteAnalysis from './components/SiteAnalysis.vue';
16 - import VisitSource from './components/VisitSource.vue';  
17 - import VisitRadar from './components/VisitRadar.vue';  
18 - import SalesProductPie from './components/SalesProductPie.vue';  
19 - 21 + import { Card } from 'ant-design-vue';
  22 + import HelpDoc from './components/HelpDoc.vue';
20 const loading = ref(true); 23 const loading = ref(true);
21 -  
22 setTimeout(() => { 24 setTimeout(() => {
23 loading.value = false; 25 loading.value = false;
24 }, 1500); 26 }, 1500);
1 <template> 1 <template>
2 <div class="step3"> 2 <div class="step3">
3 - <h1 v-if="alarmList.length === 0" style="font-size: 24px" class="text-center"  
4 - >未配置报警规则</h1  
5 - >  
6 -  
7 - <template v-else v-for="(item, index) in alarmList" :key="item">  
8 - <CollapseContainer :title="item.alarmType" style="border: 1px solid #bfbfbf" class="mb-6"> 3 + <template v-for="(item, index) in alarmList" :key="item.id">
  4 + <CollapseContainer class="border mb-8">
9 <template #action> 5 <template #action>
10 - <div @click="handleDeleteAlarm(index)" class="cursor-pointer"> 6 + <div @click="deleteAlarmRule(index)" class="cursor-pointer">
11 <DeleteOutlined style="font-size: 20px" class="mr-2" /> 7 <DeleteOutlined style="font-size: 20px" class="mr-2" />
12 </div> 8 </div>
13 </template> 9 </template>
14 - <a-form :wrapper-col="wrapperCol" labelAlign="left" :model="item" :rules="rules">  
15 - <a-form-item label="报警类型" :labelCol="{ style: { width: '80px' } }" name="alarmType">  
16 - <a-input v-model:value="item.alarmType" />  
17 - </a-form-item>  
18 - </a-form>  
19 - 10 + <BasicForm @register="registerForm" />
20 <CollapseContainer> 11 <CollapseContainer>
21 <template #action> 高级设置 </template> 12 <template #action> 高级设置 </template>
22 - <div class="flex" style="align-items: center">  
23 - <input type="checkbox" v-model="item.isPass" /> <div class="ml-2">传递警报</div>  
24 - </div>  
25 -  
26 - <a-form :wrapper-col="wrapperCol" labelAlign="left" v-if="item.isPass">  
27 - <a-form-item label="传递的关联类型" :labelCol="{ style: { width: '120px' } }">  
28 - <a-input />  
29 - </a-form-item>  
30 - </a-form> 13 + <BasicForm @register="registerFormHighSetting">
  14 + <template #checkBox="{ model, field }">
  15 + <Checkbox v-model:checked="model[field]">传递报警</Checkbox>
  16 + </template>
  17 + </BasicForm>
31 </CollapseContainer> 18 </CollapseContainer>
32 - <p style="color: #3c3c3c">创建报警规则</p>  
33 - <template v-for="(item1, index1) in item.alarmRule" :key="item1">  
34 - <div class="alarm-rule mb-4">  
35 - <div style="width: 90%; border: 2px solid #8c8c8c; border-radius: 5px" class="flex">  
36 - <div style="width: 30%; height: 100%; border-right: 1px solid #e0e0e0">  
37 - <span style="color: #305680; margin-left: 10px">严重程度</span>  
38 - <a-select :options="options" style="width: 100px; margin-left: 10px" />  
39 - </div>  
40 - <div style="width: 70%; height: 100%"> 19 + <p>创建报警规则</p>
  20 + <template v-for="(createItem, createIndex) in item.createRule" :key="createItem.id">
  21 + <div class="aic mb-4" style="border: 1px solid #bfbfbf">
  22 + <div class="w-3/4">
  23 + <BasicForm @register="registerFormCreateAlarm" />
  24 + <div>
41 <p style="color: #f5594e" class="mt-4 ml-4" 25 <p style="color: #f5594e" class="mt-4 ml-4"
42 >请添加报警规则条件 26 >请添加报警规则条件
43 <PlusOutlined class="cursor-pointer ml-4" style="font-size: 20px" 27 <PlusOutlined class="cursor-pointer ml-4" style="font-size: 20px"
@@ -47,68 +31,29 @@ @@ -47,68 +31,29 @@
47 <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px" 31 <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px"
48 /></p> 32 /></p>
49 <p class="mt-4 ml-4" 33 <p class="mt-4 ml-4"
50 - >详情:1  
51 - <EditOutlined  
52 - @click="editCreateDetail(index, index1)"  
53 - class="cursor-pointer ml-4"  
54 - style="font-size: 20px" 34 + >详情:<EditOutlined class="cursor-pointer ml-4" style="font-size: 20px"
55 /></p> 35 /></p>
56 - <p class="mt-4 ml-4">dashboard: <a-select style="width: 180px" /></p> 36 + <p class="mt-4 ml-4">dashboard:</p>
57 </div> 37 </div>
58 </div> 38 </div>
59 - <div style="width: 10%" class="alarm-remove">  
60 - <a-tooltip title="移除"> 39 + <div class="w-1/4 flex justify-center">
  40 + <Tooltip title="移除">
61 <MinusCircleOutlined 41 <MinusCircleOutlined
62 - style="font-size: 25px color:#305680" 42 + style="font-size: 25px; color: #305680"
63 class="cursor-pointer" 43 class="cursor-pointer"
64 - @click="handleDeleteCondition(index, index1)" 44 + @click="deleteCondition(index, createIndex)"
65 /> 45 />
66 - </a-tooltip> 46 + </Tooltip>
67 </div> 47 </div>
68 </div> 48 </div>
69 </template> 49 </template>
70 <a-button class="mt-5" @click="addCreateRole(index)" 50 <a-button class="mt-5" @click="addCreateRole(index)"
71 ><PlusCircleOutlined />添加创建条件</a-button 51 ><PlusCircleOutlined />添加创建条件</a-button
72 > 52 >
73 - <!-- 创建报警规则的弹框 -->  
74 - <a-modal v-model:visible="visible" title="详情" centered>  
75 - <a-textarea placeholder="报警详细信息" :rows="4" />  
76 - </a-modal>  
77 -  
78 - <p style="color: #3c3c3c">清除报警规则</p>  
79 - <template v-for="(item2, index2) in item.removeRule" :key="item2">  
80 - <div class="alarm-rule mb-4">  
81 - <div style="width: 90%; border: 2px solid #8c8c8c; border-radius: 5px" class="flex">  
82 - <div style="width: 70%; height: 100%">  
83 - <p style="color: #f5594e" class="mt-4 ml-4"  
84 - >请添加报警规则条件  
85 - <PlusOutlined class="cursor-pointer ml-4" style="font-size: 20px"  
86 - /></p>  
87 - <p class="mt-4 ml-4"  
88 - >启用规则:始终启用  
89 - <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px"  
90 - /></p>  
91 - <p class="mt-4 ml-4"  
92 - >详情:1 <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px" />  
93 - </p>  
94 -  
95 - <p class="mt-4 ml-4">Mobile dashboard: <a-select style="width: 150px" /></p>  
96 - </div>  
97 - </div>  
98 - <div style="width: 10%" class="alarm-remove">  
99 - <a-tooltip title="移除">  
100 - <MinusCircleOutlined  
101 - style="font-size: 25px color:#305680"  
102 - class="cursor-pointer"  
103 - @click="handleDeleteRemoveCondition(index, index2)"  
104 - />  
105 - </a-tooltip>  
106 - </div>  
107 - </div>  
108 - </template>  
109 - <a-button class="mt-5" @click="addRemoveRule(index)"  
110 - ><PlusCircleOutlined />添加清除条件</a-button  
111 - > 53 + <p>清除报警规则</p>
  54 + <BasicForm @register="registerFormClearAlarm">
  55 + <template #formHeader> </template>
  56 + </BasicForm>
112 </CollapseContainer> 57 </CollapseContainer>
113 </template> 58 </template>
114 <div class="flex justify-start"> 59 <div class="flex justify-start">
@@ -117,163 +62,156 @@ @@ -117,163 +62,156 @@
117 </div> 62 </div>
118 </div> 63 </div>
119 </template> 64 </template>
  65 +
120 <script lang="ts"> 66 <script lang="ts">
121 - import { defineComponent, ref } from 'vue';  
122 - import {  
123 - Alert,  
124 - Divider,  
125 - Descriptions,  
126 - Input,  
127 - Form,  
128 - Button,  
129 - Select,  
130 - Tooltip,  
131 - Modal,  
132 - Textarea,  
133 - } from 'ant-design-vue'; 67 + import { defineComponent, ref, unref } from 'vue';
  68 + import type { alarmListItem } from '../types/index';
  69 + import { CollapseContainer } from '/@/components/Container/index';
  70 + import { BasicForm, useForm } from '/@/components/Form';
  71 + import { step3Schemas, step3HighSetting, step3CreateAlarm, step3ClearAlarm } from './data';
134 import { 72 import {
135 DeleteOutlined, 73 DeleteOutlined,
136 MinusCircleOutlined, 74 MinusCircleOutlined,
137 PlusCircleOutlined, 75 PlusCircleOutlined,
138 PlusOutlined, 76 PlusOutlined,
139 EditOutlined, 77 EditOutlined,
  78 + CloseOutlined,
140 } from '@ant-design/icons-vue'; 79 } from '@ant-design/icons-vue';
141 -  
142 - import { CollapseContainer } from '/@/components/Container/index';  
143 - interface alarmListItem {  
144 - alarmType: string;  
145 - isPass: boolean;  
146 - alarmRule: [];  
147 - removeRule: [];  
148 - } 80 + import { Tooltip, Checkbox } from 'ant-design-vue';
149 export default defineComponent({ 81 export default defineComponent({
150 components: { 82 components: {
  83 + BasicForm,
  84 + CollapseContainer,
151 DeleteOutlined, 85 DeleteOutlined,
152 MinusCircleOutlined, 86 MinusCircleOutlined,
153 PlusCircleOutlined, 87 PlusCircleOutlined,
154 - CollapseContainer,  
155 - EditOutlined,  
156 PlusOutlined, 88 PlusOutlined,
157 - [Modal.name]: Modal,  
158 - [Textarea.name]: Textarea,  
159 - [Tooltip.name]: Tooltip,  
160 - [Button.name]: Button,  
161 - [Input.name]: Input,  
162 - [Select.name]: Select,  
163 - [Form.name]: Form,  
164 - [Form.Item.name]: Form.Item,  
165 - [Alert.name]: Alert,  
166 - [Divider.name]: Divider,  
167 - [Descriptions.name]: Descriptions,  
168 - [Descriptions.Item.name]: Descriptions.Item, 89 + EditOutlined,
  90 + CloseOutlined,
  91 + Checkbox,
  92 + Tooltip,
169 }, 93 },
170 emits: ['prev'], 94 emits: ['prev'],
171 setup(_, { emit }) { 95 setup(_, { emit }) {
172 - const alarmList = ref<alarmListItem[]>([]);  
173 - const visible = ref(false);  
174 - const options = ref([  
175 - {  
176 - value: '1',  
177 - label: '危险',  
178 - },  
179 -  
180 - {  
181 - value: '2',  
182 - label: '重要',  
183 - },  
184 - {  
185 - value: '3',  
186 - label: '次要',  
187 - },  
188 - {  
189 - value: '4',  
190 - label: '警告',  
191 - },  
192 - {  
193 - value: '5',  
194 - label: '不确定',  
195 - },  
196 - ]);  
197 - const rules = {  
198 - alarmType: [  
199 - {  
200 - required: true,  
201 - message: '报警类型必填',  
202 - trigger: 'blur',  
203 - },  
204 - ], 96 + //告警列表
  97 + let alarmList = ref<alarmListItem[]>([]);
  98 + // 添加和删除告警配置
  99 + const deleteAlarmRule = (index: number) => {
  100 + unref(alarmList).splice(index, 1);
205 }; 101 };
206 - 102 + // 上一步
207 const prevStep = () => { 103 const prevStep = () => {
208 emit('prev'); 104 emit('prev');
209 }; 105 };
210 - // 添加报警规则  
211 const addAlarmRule = () => { 106 const addAlarmRule = () => {
212 - alarmList.value.push({ 107 + unref(alarmList).push({
  108 + id: Date.now(),
213 alarmType: '', 109 alarmType: '',
214 isPass: false, 110 isPass: false,
215 - alarmRule: [],  
216 - removeRule: [], 111 + createRule: [
  112 + {
  113 + id: Date.now() + Math.random(),
  114 + alarmVisible: false,
  115 + addKeyFilterVisible: false,
  116 + detailVisible: false,
  117 + detail: '',
  118 + filterList: [],
  119 + },
  120 + ],
  121 + clearRule: [],
217 }); 122 });
218 }; 123 };
219 - const handleDeleteAlarm = (index) => {  
220 - alarmList.value.splice(index, 1);  
221 - };  
222 - // 添加创建条件  
223 - const addCreateRole = (index) => {  
224 - alarmList.value[index].alarmRule.push({ 124 +
  125 + // 表单部分 报警类型
  126 + const [registerForm] = useForm({
  127 + labelWidth: 120,
  128 + schemas: step3Schemas,
  129 + showResetButton: false,
  130 + showSubmitButton: false,
  131 + });
  132 +
  133 + // 高级设置
  134 + const [registerFormHighSetting] = useForm({
  135 + labelWidth: 120,
  136 + schemas: step3HighSetting,
  137 + showResetButton: false,
  138 + showSubmitButton: false,
  139 + actionColOptions: {
  140 + span: 24,
  141 + },
  142 + });
  143 +
  144 + // 添加创建条件表单
  145 + const [registerFormCreateAlarm] = useForm({
  146 + labelWidth: 120,
  147 + schemas: step3CreateAlarm,
  148 + showResetButton: false,
  149 + showSubmitButton: false,
  150 + actionColOptions: {
  151 + span: 24,
  152 + },
  153 + });
  154 +
  155 + // 清除条件表单
  156 + const [registerFormClearAlarm] = useForm({
  157 + labelWidth: 120,
  158 + schemas: step3ClearAlarm,
  159 + showResetButton: false,
  160 + showSubmitButton: false,
  161 + actionColOptions: {
  162 + span: 24,
  163 + },
  164 + });
  165 +
  166 + // 添加‘创建条件’
  167 + const addCreateRole = (index: number) => {
  168 + unref(alarmList)[index].createRule.push({
  169 + id: Date.now() + Math.random(),
  170 + alarmVisible: false,
  171 + addKeyFilterVisible: false,
  172 + detailVisible: false,
225 detail: '', 173 detail: '',
  174 + filterList: [],
226 }); 175 });
227 }; 176 };
228 - const handleDeleteCondition = (index, index1) => {  
229 - alarmList.value[index].alarmRule.splice(index1, 1);  
230 - };  
231 -  
232 - // 清除报警规则  
233 - const addRemoveRule = (index) => {  
234 - alarmList.value[index].removeRule.push('1');  
235 - };  
236 - const handleDeleteRemoveCondition = (index, index1) => {  
237 - alarmList.value[index].removeRule.splice(index1, 1); 177 + // 删除‘创建条件’
  178 + const deleteCondition = (index: number, createIndex: number) => {
  179 + alarmList.value[index].createRule.splice(createIndex, 1);
238 }; 180 };
239 -  
240 - // 编辑创建规则的详情  
241 - const editCreateDetail = (index, index1) => {  
242 - visible.value = true;  
243 - console.log(alarmList.value[index].alarmRule[index1].detail);  
244 - };  
245 -  
246 return { 181 return {
247 - rules,  
248 alarmList, 182 alarmList,
249 - options,  
250 - visible, 183 + deleteAlarmRule,
251 prevStep, 184 prevStep,
252 addAlarmRule, 185 addAlarmRule,
  186 +
  187 + registerForm,
  188 +
  189 + registerFormHighSetting,
  190 +
  191 + registerFormCreateAlarm,
253 addCreateRole, 192 addCreateRole,
254 - handleDeleteAlarm,  
255 - handleDeleteCondition,  
256 - addRemoveRule,  
257 - handleDeleteRemoveCondition,  
258 - editCreateDetail,  
259 - labelCol: { style: { width: '150px' } },  
260 - wrapperCol: { span: 18 }, 193 + deleteCondition,
  194 +
  195 + registerFormClearAlarm,
261 }; 196 };
262 }, 197 },
263 }); 198 });
264 </script> 199 </script>
  200 +
265 <style lang="less" scoped> 201 <style lang="less" scoped>
266 .step3 { 202 .step3 {
267 - width: 500px;  
268 - margin: 0 auto; 203 + width: 100%;
269 } 204 }
270 - .alarm-rule {  
271 - height: 200px; 205 + .border {
  206 + border: 1px solid #bfbfbf;
  207 + }
  208 +
  209 + .aic {
272 display: flex; 210 display: flex;
273 - .alarm-remove {  
274 - display: flex;  
275 - justify-content: center;  
276 - align-items: center;  
277 - } 211 + align-items: center;
  212 + }
  213 +
  214 + :deep(.vben-collapse-container__header) {
  215 + border: none;
278 } 216 }
279 </style> 217 </style>
  1 +<template>
  2 + <div class="step3">
  3 + <h1 v-if="alarmList.length === 0" style="font-size: 24px" class="text-center"
  4 + >未配置报警规则</h1
  5 + >
  6 +
  7 + <template v-else v-for="(item, index) in alarmList" :key="item">
  8 + <CollapseContainer :title="item.alarmType" style="border: 1px solid #bfbfbf" class="mb-6">
  9 + <template #action>
  10 + <div @click="handleDeleteAlarm(index)" class="cursor-pointer">
  11 + <DeleteOutlined style="font-size: 20px" class="mr-2" />
  12 + </div>
  13 + </template>
  14 + <a-form :wrapper-col="wrapperCol" labelAlign="left" :model="item" :rules="rules">
  15 + <a-form-item label="报警类型" :labelCol="{ style: { width: '80px' } }" name="alarmType">
  16 + <a-input v-model:value="item.alarmType" />
  17 + </a-form-item>
  18 + </a-form>
  19 +
  20 + <CollapseContainer>
  21 + <template #action> 高级设置 </template>
  22 + <div class="flex" style="align-items: center">
  23 + <input type="checkbox" v-model="item.isPass" /> <div class="ml-2">传递警报</div>
  24 + </div>
  25 +
  26 + <a-form :wrapper-col="wrapperCol" labelAlign="left" v-if="item.isPass">
  27 + <a-form-item label="传递的关联类型" :labelCol="{ style: { width: '120px' } }">
  28 + <a-input />
  29 + </a-form-item>
  30 + </a-form>
  31 + </CollapseContainer>
  32 + <p style="color: #3c3c3c">创建报警规则</p>
  33 + <template v-for="(item1, index1) in item.alarmRule" :key="item1">
  34 + <div class="alarm-rule mb-4">
  35 + <div style="width: 90%; border: 2px solid #8c8c8c; border-radius: 5px" class="flex">
  36 + <div style="width: 30%; height: 100%; border-right: 1px solid #e0e0e0">
  37 + <span style="color: #305680; margin-left: 10px">严重程度</span>
  38 + <a-select :options="options" style="width: 100px; margin-left: 10px" />
  39 + </div>
  40 + <div style="width: 70%; height: 100%">
  41 + <p style="color: #f5594e" class="mt-4 ml-4"
  42 + >请添加报警规则条件
  43 + <PlusOutlined
  44 + class="cursor-pointer ml-4"
  45 + style="font-size: 20px"
  46 + @click="editAlarmCondition(index, index1)"
  47 + /></p>
  48 + <p class="mt-4 ml-4"
  49 + >启用规则:始终启用
  50 + <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px"
  51 + /></p>
  52 + <p class="mt-4 ml-4"
  53 + >详情:{{ item1.detail }}
  54 + <EditOutlined
  55 + @click="item1.detailVisible = true"
  56 + class="cursor-pointer ml-4"
  57 + style="font-size: 20px"
  58 + /></p>
  59 + <p class="mt-4 ml-4">dashboard: <a-select style="width: 180px" /></p>
  60 + </div>
  61 + </div>
  62 + <div style="width: 10%" class="alarm-remove">
  63 + <a-tooltip title="移除">
  64 + <MinusCircleOutlined
  65 + style="font-size: 25px color:#305680"
  66 + class="cursor-pointer"
  67 + @click="handleDeleteCondition(index, index1)"
  68 + />
  69 + </a-tooltip>
  70 + </div>
  71 + </div>
  72 + <!-- 编辑报警规则条件 -->
  73 + <a-modal
  74 + v-model:visible="item1.alarmVisible"
  75 + title="编辑报警规则条件"
  76 + centered
  77 + width="800px"
  78 + @cancel="handleAlarmCancel(index, index1)"
  79 + @ok="item1.alarmVisible = false"
  80 + >
  81 + <CollapseContainer title="键名筛选器" style="border: 1px solid #bfbfbf" class="mb-6">
  82 + <a-modal
  83 + v-model:visible="item1.addKeyFilterVisible"
  84 + title="添加键名筛选器"
  85 + centered
  86 + width="600px"
  87 + @ok="item1.addKeyFilterVisible = false"
  88 + >
  89 + <a-form
  90 + :wrapper-col="wrapperCol"
  91 + :labelCol="{ style: { width: '80px' } }"
  92 + labelAlign="right"
  93 + >
  94 + <a-form-item label="键类型">
  95 + <a-select :options="keyTypeOptions" default-value="Timeseries" />
  96 + </a-form-item>
  97 + <a-form-item label="键名">
  98 + <a-input />
  99 + </a-form-item>
  100 + <a-form-item label="值类型">
  101 + <a-select :options="valueTypeOptions" />
  102 + </a-form-item>
  103 + </a-form>
  104 + <collapseContainer title="筛选器">
  105 + <template v-for="filterItem in item1.filterList" :key="filterItem">
  106 + <div class="flex justify-between mb-4" style="align-items: center">
  107 + <a-select
  108 + style="width: 150px"
  109 + :options="operatorOptions"
  110 + default-value="="
  111 + /><a-input style="width: 350px" />
  112 + <a-tooltip title="删除筛选器">
  113 + <CloseOutlined @click="deleteFilter(index, index1)" />
  114 + </a-tooltip>
  115 + </div>
  116 + </template>
  117 + <div class="flex">
  118 + <a-button type="primary" class="mr-4" @click="addFilter(index, index1)"
  119 + >添加</a-button
  120 + >
  121 + <a-button type="primary">添加复合</a-button>
  122 + </div>
  123 + </collapseContainer>
  124 + </a-modal>
  125 + <div class="flex justify-start">
  126 + <a-button type="primary" @click="item1.addKeyFilterVisible = true"
  127 + >添加键名筛选器</a-button
  128 + >
  129 + </div>
  130 + </CollapseContainer>
  131 + <CollapseContainer title="筛选器预览" style="border: 1px solid #bfbfbf" class="mb-6" />
  132 + </a-modal>
  133 + <!-- 启用规则-->
  134 + <!-- <a-modal
  135 + v-model:visible="visible"
  136 + title="编辑报警规则条件"
  137 + centered
  138 + @cancel="handleCancel(index, index1)"
  139 + @ok="visible = false"
  140 + >
  141 + 编辑报警规则条件123
  142 + </a-modal> -->
  143 +
  144 + <!-- 创建报警规则详情的弹框 -->
  145 + <a-modal
  146 + v-model:visible="item1.detailVisible"
  147 + title="详情"
  148 + centered
  149 + @cancel="handleCancel(index, index1)"
  150 + @ok="item1.detailVisible = false"
  151 + >
  152 + <a-textarea v-model:value="item1.detail" placeholder="报警详细信息" :rows="4" />
  153 + </a-modal>
  154 + </template>
  155 + <a-button class="mt-5" @click="addCreateRole(index)"
  156 + ><PlusCircleOutlined />添加创建条件</a-button
  157 + >
  158 + <p style="color: #3c3c3c">清除报警规则</p>
  159 + <template v-for="(item2, index2) in item.removeRule" :key="item2">
  160 + <div class="alarm-rule mb-4">
  161 + <div style="width: 90%; border: 2px solid #8c8c8c; border-radius: 5px" class="flex">
  162 + <div style="width: 70%; height: 100%">
  163 + <p style="color: #f5594e" class="mt-4 ml-4"
  164 + >请添加报警规则条件
  165 + <PlusOutlined class="cursor-pointer ml-4" style="font-size: 20px"
  166 + /></p>
  167 + <p class="mt-4 ml-4"
  168 + >启用规则:始终启用
  169 + <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px"
  170 + /></p>
  171 + <p class="mt-4 ml-4"
  172 + >详情:{{ item2.detail }}
  173 + <EditOutlined
  174 + class="cursor-pointer ml-4"
  175 + style="font-size: 20px"
  176 + @click="editRemoveVisible = true"
  177 + />
  178 + </p>
  179 +
  180 + <p class="mt-4 ml-4">Mobile dashboard: <a-select style="width: 150px" /></p>
  181 + </div>
  182 + </div>
  183 + <div style="width: 10%" class="alarm-remove">
  184 + <a-tooltip title="移除">
  185 + <MinusCircleOutlined
  186 + style="font-size: 25px color:#305680"
  187 + class="cursor-pointer"
  188 + @click="handleDeleteRemoveCondition(index, index2)"
  189 + />
  190 + </a-tooltip>
  191 + </div>
  192 + </div>
  193 + <a-modal
  194 + v-model:visible="editRemoveVisible"
  195 + title="详情"
  196 + centered
  197 + @cancel="handleCancel1(index, index2)"
  198 + @ok="editRemoveVisible = false"
  199 + >
  200 + <a-textarea v-model:value="item2.detail" placeholder="报警详细信息" :rows="4" />
  201 + </a-modal>
  202 + </template>
  203 + <a-button class="mt-5" @click="addRemoveRule(index)"
  204 + ><PlusCircleOutlined />添加清除条件</a-button
  205 + >
  206 + </CollapseContainer>
  207 + </template>
  208 + <div class="flex justify-start">
  209 + <a-button class="mr-5" @click="prevStep">上一步</a-button>
  210 + <a-button type="primary" @click="addAlarmRule">添加报警规则</a-button>
  211 + </div>
  212 + </div>
  213 +</template>
  214 +<script lang="ts">
  215 + import { defineComponent, ref } from 'vue';
  216 + import {
  217 + Alert,
  218 + Divider,
  219 + Descriptions,
  220 + Input,
  221 + Form,
  222 + FormItem,
  223 + Button,
  224 + Select,
  225 + Tooltip,
  226 + Textarea,
  227 + Modal,
  228 + } from 'ant-design-vue';
  229 + import {
  230 + DeleteOutlined,
  231 + MinusCircleOutlined,
  232 + PlusCircleOutlined,
  233 + PlusOutlined,
  234 + EditOutlined,
  235 + CloseOutlined,
  236 + } from '@ant-design/icons-vue';
  237 + import { CollapseContainer } from '/@/components/Container/index';
  238 + import { alarmListItem } from '../types/index';
  239 + export default defineComponent({
  240 + components: {
  241 + DeleteOutlined,
  242 + MinusCircleOutlined,
  243 + PlusCircleOutlined,
  244 + CollapseContainer,
  245 + EditOutlined,
  246 + PlusOutlined,
  247 + CloseOutlined,
  248 + [Form.name]: Form,
  249 + [FormItem.name]: FormItem,
  250 + [Modal.name]: Modal,
  251 + [Textarea.name]: Textarea,
  252 + [Tooltip.name]: Tooltip,
  253 + [Button.name]: Button,
  254 + [Input.name]: Input,
  255 + [Select.name]: Select,
  256 + [Form.name]: Form,
  257 + [Form.Item.name]: Form.Item,
  258 + [Alert.name]: Alert,
  259 + [Divider.name]: Divider,
  260 + [Descriptions.name]: Descriptions,
  261 + [Descriptions.Item.name]: Descriptions.Item,
  262 + },
  263 + emits: ['prev'],
  264 + setup(_, { emit }) {
  265 + const alarmList = ref<alarmListItem[]>([]);
  266 +
  267 + const options = ref([
  268 + {
  269 + value: '1',
  270 + label: '危险',
  271 + },
  272 +
  273 + {
  274 + value: '2',
  275 + label: '重要',
  276 + },
  277 + {
  278 + value: '3',
  279 + label: '次要',
  280 + },
  281 + {
  282 + value: '4',
  283 + label: '警告',
  284 + },
  285 + {
  286 + value: '5',
  287 + label: '不确定',
  288 + },
  289 + ]);
  290 + const keyTypeOptions = ref([
  291 + {
  292 + value: 'properties',
  293 + label: '属性',
  294 + },
  295 +
  296 + {
  297 + value: 'Timeseries',
  298 + label: 'Timeseries',
  299 + },
  300 + {
  301 + value: 'constant',
  302 + label: '常量',
  303 + },
  304 + ]);
  305 + const valueTypeOptions = ref([
  306 + {
  307 + value: 'string',
  308 + label: '字符串',
  309 + },
  310 + {
  311 + value: 'number',
  312 + label: '数字',
  313 + },
  314 + {
  315 + value: 'boolean',
  316 + label: '布尔值',
  317 + },
  318 + {
  319 + value: 'dateTime',
  320 + label: '日期时间',
  321 + },
  322 + ]);
  323 + const operatorOptions = ref([
  324 + {
  325 + value: '=',
  326 + label: '等于',
  327 + },
  328 + {
  329 + value: '!=',
  330 + label: '不等于',
  331 + },
  332 + {
  333 + value: '>',
  334 + label: '大于',
  335 + },
  336 + {
  337 + value: '<',
  338 + label: '小于',
  339 + },
  340 + {
  341 + value: '>=',
  342 + label: '大于或等于',
  343 + },
  344 + {
  345 + value: '<=',
  346 + label: '小于或等于',
  347 + },
  348 + ]);
  349 + const rules = {
  350 + alarmType: [
  351 + {
  352 + required: true,
  353 + message: '报警类型必填',
  354 + trigger: 'blur',
  355 + },
  356 + ],
  357 + };
  358 +
  359 + const prevStep = () => {
  360 + emit('prev');
  361 + };
  362 + // 添加报警规则
  363 + const addAlarmRule = () => {
  364 + alarmList.value.push({
  365 + alarmType: '',
  366 + isPass: false,
  367 + createRule: [],
  368 + clearRule: [],
  369 + });
  370 + };
  371 + const handleDeleteAlarm = (index: number) => {
  372 + alarmList.value.splice(index, 1);
  373 + };
  374 + // 添加‘创建条件’
  375 + const addCreateRole = (index: number) => {
  376 + alarmList.value[index].createRule.push({
  377 + alarmVisible: false,
  378 + addKeyFilterVisible: false,
  379 + detailVisible: false,
  380 + detail: '',
  381 + filterList: [],
  382 + });
  383 + };
  384 + const handleDeleteCondition = (index: number, index1: number) => {
  385 + alarmList.value[index].createRule.splice(index1, 1);
  386 + };
  387 +
  388 + // 添加‘清除报警规则’
  389 + const addRemoveRule = (index: number) => {
  390 + alarmList.value[index].clearRule.push({
  391 + detail: '',
  392 + });
  393 + };
  394 + const handleDeleteRemoveCondition = (index: number, index1: number) => {
  395 + alarmList.value[index].clearRule.splice(index1, 1);
  396 + };
  397 +
  398 + // 弹框取消事件 --2个
  399 + const visible = ref(false);
  400 + const editRemoveVisible = ref(false);
  401 + const handleCancel = (index: number, index1: number) => {
  402 + alarmList.value[index].createRule[index1].detail = '';
  403 + alarmList.value[index].createRule[index1].detailVisible = false;
  404 + };
  405 +
  406 + const handleCancel1 = (index: number, index2: number) => {
  407 + alarmList.value[index].clearRule[index2].detail = '';
  408 + editRemoveVisible.value = false;
  409 + };
  410 +
  411 + const handleAlarmCancel = (index, index1) => {
  412 + alarmList.value[index].createRule[index1].alarmVisible = false;
  413 + console.log('object---');
  414 + };
  415 + const editAlarmCondition = (index, index1) => {
  416 + alarmList.value[index].createRule[index1].alarmVisible = true;
  417 + console.log('object');
  418 + };
  419 + // 添加键名筛选器
  420 + const addFilter = (index, index1) => {
  421 + console.log(index, index1);
  422 + alarmList.value[index].createRule[index1].filterList?.push({
  423 + operator: '=',
  424 + value: '',
  425 + });
  426 + };
  427 + const deleteFilter = (index: number, index1: number) => {
  428 + alarmList.value[index].createRule[index1].filterList?.splice(index1, 1);
  429 + };
  430 + return {
  431 + rules,
  432 + alarmList,
  433 + options,
  434 + visible,
  435 + editRemoveVisible,
  436 + prevStep,
  437 + addAlarmRule,
  438 + addCreateRole,
  439 + handleDeleteAlarm,
  440 + handleDeleteCondition,
  441 + addRemoveRule,
  442 + handleDeleteRemoveCondition,
  443 + handleCancel,
  444 + handleCancel1,
  445 + handleAlarmCancel,
  446 + editAlarmCondition,
  447 + keyTypeOptions,
  448 + valueTypeOptions,
  449 + operatorOptions,
  450 + deleteFilter,
  451 + addFilter,
  452 + labelCol: { style: { width: '150px' } },
  453 + wrapperCol: { span: 18 },
  454 + };
  455 + },
  456 + });
  457 +</script>
  458 +<style lang="less" scoped>
  459 + .step3 {
  460 + width: 500px;
  461 + margin: 0 auto;
  462 + }
  463 + .alarm-rule {
  464 + height: 200px;
  465 + display: flex;
  466 + .alarm-remove {
  467 + display: flex;
  468 + justify-content: center;
  469 + align-items: center;
  470 + }
  471 + }
  472 +</style>
@@ -46,11 +46,73 @@ export const step2Schemas: FormSchema[] = [ @@ -46,11 +46,73 @@ export const step2Schemas: FormSchema[] = [
46 export const step3Schemas: FormSchema[] = [ 46 export const step3Schemas: FormSchema[] = [
47 { 47 {
48 field: 'transportType', 48 field: 'transportType',
  49 + component: 'Input',
  50 + label: '报警类型',
  51 + required: true,
  52 + componentProps: {
  53 + placeholder: '请输入报警类型',
  54 + },
  55 + },
  56 +];
  57 +
  58 +export const step3HighSetting: FormSchema[] = [
  59 + {
  60 + field: 'isPass',
  61 + component: 'Checkbox',
  62 + label: '',
  63 + slot: 'checkBox',
  64 + },
  65 + {
  66 + field: 'associationType',
  67 + component: 'Input',
  68 + label: '关联类型',
  69 + componentProps: {
  70 + placeholder: '要传递的关联类型',
  71 + },
  72 + ifShow: ({ values }) => !!values.isPass,
  73 + },
  74 +];
  75 +
  76 +export const step3CreateAlarm: FormSchema[] = [
  77 + {
  78 + field: 'severity',
49 component: 'Select', 79 component: 'Select',
50 - label: '报警规则',  
51 - defaultValue: 'DEFAULT', 80 + label: '严重程度',
52 componentProps: { 81 componentProps: {
53 - options: [{ label: '默认', value: 'DEFAULT' }], 82 + placeholder: '请选择严重程度',
  83 + options: [
  84 + {
  85 + value: '1',
  86 + label: '危险',
  87 + },
  88 + {
  89 + value: '2',
  90 + label: '重要',
  91 + },
  92 + {
  93 + value: '3',
  94 + label: '次要',
  95 + },
  96 + {
  97 + value: '4',
  98 + label: '警告',
  99 + },
  100 + {
  101 + value: '5',
  102 + label: '不确定',
  103 + },
  104 + ],
54 }, 105 },
55 }, 106 },
56 ]; 107 ];
  108 +
  109 +export const step3ClearAlarm: FormSchema[] = [
  110 + // {
  111 + // field: 'associationType',
  112 + // component: 'Input',
  113 + // label: '关联类型',
  114 + // componentProps: {
  115 + // placeholder: '要传递的关联类型',
  116 + // },
  117 + // },
  118 +];
  1 +interface alarmRuleFilter {
  2 + operator: string;
  3 + value: string;
  4 +}
  5 +interface createRule {
  6 + id: number;
  7 + alarmVisible: boolean;
  8 + addKeyFilterVisible: boolean;
  9 + detailVisible: boolean;
  10 + detail: string;
  11 + filterList?: alarmRuleFilter[];
  12 +}
  13 +interface clearRule {
  14 + detail: string;
  15 +}
  16 +export interface alarmListItem {
  17 + id: number;
  18 + alarmType: string;
  19 + isPass: boolean;
  20 + createRule: createRule[];
  21 + clearRule: clearRule[];
  22 +}
  1 +<template>
  2 + <div class="card">
  3 + <Card :bordered="false" class="card">
  4 + <BasicForm @register="registerForm">
  5 + <template #logoUpload>
  6 + <Upload
  7 + name="avatar"
  8 + list-type="picture-card"
  9 + class="avatar-uploader"
  10 + :show-upload-list="false"
  11 + @preview="handlePreview"
  12 + :customRequest="customUploadLogoPic"
  13 + :before-upload="beforeUploadLogoPic"
  14 + >
  15 + <img v-if="logoPic" :src="logoPic" />
  16 + <div v-else>
  17 + <div style="margin-top: 30px">
  18 + <PlusOutlined style="font-size: 30px" />
  19 + </div>
  20 + <div
  21 + class="ant-upload-text flex"
  22 + style="width: 180px; height: 130px; align-items: center"
  23 + >
  24 + 支持.PNG、.JPG、.SVG格式,建议尺寸32px × 32px,大小不超过500KB。</div
  25 + >
  26 + </div>
  27 + </Upload>
  28 + </template>
  29 + <template #bgUpload>
  30 + <Upload
  31 + name="avatar"
  32 + list-type="picture-card"
  33 + class="avatar-uploader"
  34 + :show-upload-list="false"
  35 + :customRequest="customUploadBgPic"
  36 + :before-upload="beforeUploadBgPic"
  37 + >
  38 + <img v-if="bgPic" :src="bgPic" alt="avatar" />
  39 + <div v-else>
  40 + <div style="margin-top: 30px">
  41 + <PlusOutlined style="font-size: 30px" />
  42 + </div>
  43 + <div
  44 + class="ant-upload-text flex"
  45 + style="width: 280px; height: 130px; align-items: center"
  46 + >
  47 + 支持.PNG、.JPG、.SVG格式,建议尺寸为1250px × 730px(及以上),大小不超过5M。</div
  48 + >
  49 + </div>
  50 + </Upload>
  51 + </template>
  52 + <template #colorInput="{ model, field }"
  53 + ><Input disabled v-model:value="model[field]">
  54 + <template #prefix> <input type="color" v-model="model[field]" /> </template
  55 + ></Input>
  56 + </template>
  57 +
  58 + <template #homeSwiper>
  59 + <Upload
  60 + v-model:file-list="fileList"
  61 + list-type="picture-card"
  62 + @preview="handlePreview"
  63 + :customRequest="customUploadHomeSwiper"
  64 + :before-upload="beforeUploadHomeSwiper"
  65 + >
  66 + <div v-if="fileList.length < 5">
  67 + <plus-outlined />
  68 + <div class="ant-upload-text">Upload</div>
  69 + </div>
  70 + </Upload>
  71 + <Modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
  72 + <img alt="example" style="width: 100%" :src="previewImage" />
  73 + </Modal>
  74 + </template>
  75 + </BasicForm>
  76 + </Card>
  77 + <Loading v-bind="compState" />
  78 + <a-button
  79 + @click="handleUpdateInfo"
  80 + size="large"
  81 + type="primary"
  82 + style="margin-top: 20px; background-color: #2950f7; border-radius: 5px"
  83 + >保存信息</a-button
  84 + >
  85 + </div>
  86 +</template>
  87 +
  88 +<script lang="ts">
  89 + import { defineComponent, ref, unref, onMounted } from 'vue';
  90 + import { BasicForm, useForm } from '/@/components/Form/index';
  91 + import { Loading } from '/@/components/Loading/index';
  92 + import { Card, Upload, Input, Modal } from 'ant-design-vue';
  93 + import { PlusOutlined } from '@ant-design/icons-vue';
  94 + import { schemas } from '../config/AppDraw.config';
  95 + import { FileItem, FileInfo } from '../types/index';
  96 + import { logoUpload, bgUpload } from '/@/api/oem/index';
  97 + import { useMessage } from '/@/hooks/web/useMessage';
  98 + import { getAppDesign, updateAppDesign } from '/@/api/oem/index';
  99 + export default defineComponent({
  100 + components: {
  101 + Card,
  102 + BasicForm,
  103 + Upload,
  104 + Loading,
  105 + PlusOutlined,
  106 + Input,
  107 + Modal,
  108 + },
  109 + setup() {
  110 + const compState = ref({
  111 + absolute: false,
  112 + loading: false,
  113 + tip: '拼命加载中...',
  114 + });
  115 + const { createMessage } = useMessage();
  116 + const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
  117 + schemas,
  118 + showSubmitButton: false,
  119 + showResetButton: false,
  120 + labelWidth: 150,
  121 + wrapperCol: {
  122 + span: 10,
  123 + },
  124 + });
  125 + const previewVisible = ref<boolean>(false);
  126 + const previewImage = ref<string | undefined>('');
  127 + function getBase64(file: File) {
  128 + return new Promise((resolve, reject) => {
  129 + const reader = new FileReader();
  130 + reader.readAsDataURL(file);
  131 + reader.onload = () => resolve(reader.result);
  132 + reader.onerror = (error) => reject(error);
  133 + });
  134 + }
  135 + const handleCancel = () => {
  136 + previewVisible.value = false;
  137 + };
  138 + const handlePreview = async (file: FileItem) => {
  139 + console.log(file);
  140 + if (!file.url && !file.preview) {
  141 + file.preview = (await getBase64(file.originFileObj)) as string;
  142 + }
  143 + previewImage.value = file.url || file.preview;
  144 + previewVisible.value = true;
  145 + };
  146 + const handleChange = ({ fileList: newFileList }: FileInfo) => {
  147 + fileList.value = newFileList;
  148 + };
  149 +
  150 + // logo图片上传
  151 + const logoPic = ref();
  152 + async function customUploadLogoPic({ file }) {
  153 + if (beforeUploadLogoPic(file)) {
  154 + const formData = new FormData();
  155 + formData.append('file', file);
  156 + const response = await logoUpload(formData);
  157 + if (response.fileStaticUri) {
  158 + logoPic.value = response.fileStaticUri;
  159 + }
  160 + }
  161 + }
  162 + const beforeUploadLogoPic = (file) => {
  163 + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  164 + if (!isJpgOrPng) {
  165 + createMessage.error('只能上传图片文件!');
  166 + }
  167 + const isLt2M = (file.size as number) / 1024 < 500;
  168 + if (!isLt2M) {
  169 + createMessage.error('图片大小不能超过500KB!');
  170 + }
  171 + return isJpgOrPng && isLt2M;
  172 + };
  173 +
  174 + // 登录页背景上传
  175 + const bgPic = ref();
  176 + async function customUploadBgPic({ file }) {
  177 + if (beforeUploadBgPic(file)) {
  178 + const formData = new FormData();
  179 + formData.append('file', file);
  180 + const response = await bgUpload(formData);
  181 + if (response.fileStaticUri) {
  182 + bgPic.value = response.fileStaticUri;
  183 + }
  184 + }
  185 + }
  186 + const beforeUploadBgPic = (file) => {
  187 + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  188 + if (!isJpgOrPng) {
  189 + createMessage.error('只能上传图片文件!');
  190 + }
  191 + const isLt2M = (file.size as number) / 1024 / 1024 < 5;
  192 + if (!isLt2M) {
  193 + createMessage.error('图片大小不能超过5MB!');
  194 + }
  195 + return isJpgOrPng && isLt2M;
  196 + };
  197 + // 首页轮播图
  198 + const fileList = ref<FileItem[]>([]);
  199 + async function customUploadHomeSwiper({ file }) {
  200 + if (beforeUploadHomeSwiper(file)) {
  201 + const formData = new FormData();
  202 + formData.append('file', file);
  203 +
  204 + const response = await bgUpload(formData);
  205 + if (response.fileStaticUri) {
  206 + fileList.value.push({
  207 + uid: -Math.random() + '',
  208 + name: response.fileName,
  209 + status: 'done',
  210 + url: response.fileStaticUri,
  211 + });
  212 + console.log('改变前', fileList.value);
  213 + const fileArr = fileList.value.filter((item) => {
  214 + return item.percent !== 0;
  215 + });
  216 + fileList.value = fileArr;
  217 + }
  218 + }
  219 + }
  220 +
  221 + const beforeUploadHomeSwiper = (file) => {
  222 + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  223 + if (!isJpgOrPng) {
  224 + createMessage.error('只能上传图片文件!');
  225 + }
  226 + const isLt2M = (file.size as number) / 1024 / 1024 < 5;
  227 + if (!isLt2M) {
  228 + createMessage.error('图片大小不能超过5MB!');
  229 + }
  230 + return isJpgOrPng && isLt2M;
  231 + };
  232 +
  233 + const handleUpdateInfo = async () => {
  234 + try {
  235 + console.log(fileList.value);
  236 + const fieldValue = getFieldsValue();
  237 + // 做换字段
  238 + const homeSwiper = fileList.value.map((item) => item.url);
  239 + const rotation = homeSwiper.join(',');
  240 +
  241 + compState.value.loading = true;
  242 + await updateAppDesign({
  243 + ...fieldValue,
  244 + background: unref(bgPic),
  245 + icon: unref(bgPic),
  246 + logo: unref(logoPic),
  247 + rotation,
  248 + });
  249 + compState.value.loading = false;
  250 + createMessage.success('保存信息成功');
  251 + } catch (e) {
  252 + createMessage.error('保存信息失败');
  253 + }
  254 + };
  255 +
  256 + onMounted(async () => {
  257 + const res = await getAppDesign();
  258 + const rotation = res.rotation.split(',');
  259 + const arr: any[] = [];
  260 + for (let item of rotation) {
  261 + arr.push({
  262 + uid: -Math.random() + '',
  263 + name: '111',
  264 + url: item,
  265 + status: 'done',
  266 + });
  267 + }
  268 + setFieldsValue(res);
  269 + logoPic.value = res.logo;
  270 + bgPic.value = res.background;
  271 + if (arr[0].url === '') return;
  272 + fileList.value = arr;
  273 + });
  274 + return {
  275 + compState,
  276 + fileList,
  277 + registerForm,
  278 + handleUpdateInfo,
  279 + handleCancel,
  280 + handlePreview,
  281 + customUploadLogoPic,
  282 + beforeUploadLogoPic,
  283 + customUploadBgPic,
  284 + beforeUploadBgPic,
  285 + customUploadHomeSwiper,
  286 + beforeUploadHomeSwiper,
  287 + handleChange,
  288 + logoPic,
  289 + bgPic,
  290 + previewVisible,
  291 + previewImage,
  292 + };
  293 + },
  294 + });
  295 +</script>
  296 +
  297 +<style lang="less" scoped>
  298 + .ant-upload-select-picture-card i {
  299 + font-size: 32px;
  300 + color: #999;
  301 + }
  302 +
  303 + .ant-upload-select-picture-card .ant-upload-text {
  304 + margin-top: 8px;
  305 + color: #666;
  306 + }
  307 +</style>
  1 +<template>
  2 + <div class="card">
  3 + <Card :bordered="false" class="card">
  4 + <BasicForm @register="registerForm">
  5 + <template #logoUpload>
  6 + <Upload
  7 + name="avatar"
  8 + list-type="picture-card"
  9 + class="avatar-uploader"
  10 + :show-upload-list="false"
  11 + :customRequest="customUploadLogoPic"
  12 + :before-upload="beforeUploadLogoPic"
  13 + >
  14 + <img v-if="logoPic" :src="logoPic" />
  15 + <div v-else>
  16 + <div style="margin-top: 30px">
  17 + <PlusOutlined style="font-size: 30px" />
  18 + </div>
  19 + <div
  20 + class="ant-upload-text flex"
  21 + style="width: 180px; height: 130px; align-items: center"
  22 + >
  23 + 支持.PNG、.JPG、.SVG格式,建议尺寸32px × 32px,大小不超过500KB。</div
  24 + >
  25 + </div>
  26 + </Upload>
  27 + </template>
  28 + <template #iconUpload>
  29 + <Upload
  30 + name="avatar"
  31 + list-type="picture-card"
  32 + class="avatar-uploader"
  33 + :show-upload-list="false"
  34 + :customRequest="customUploadIconPic"
  35 + :before-upload="beforeUploadIconPic"
  36 + >
  37 + <div v-if="iconPic">
  38 + <img :src="iconPic" />
  39 + <div style="background-color: #ccc">重新上传</div>
  40 + </div>
  41 + <div v-else>
  42 + <PlusOutlined style="font-size: 30px" />
  43 + </div>
  44 + </Upload>
  45 + </template>
  46 + <template #bgUpload>
  47 + <Upload
  48 + name="avatar"
  49 + list-type="picture-card"
  50 + class="avatar-uploader"
  51 + :show-upload-list="false"
  52 + :customRequest="customUploadBgPic"
  53 + :before-upload="beforeUploadBgPic"
  54 + >
  55 + <img v-if="bgPic" :src="bgPic" alt="avatar" />
  56 + <div v-else>
  57 + <div style="margin-top: 30px">
  58 + <PlusOutlined style="font-size: 30px" />
  59 + </div>
  60 + <div
  61 + class="ant-upload-text flex"
  62 + style="width: 280px; height: 130px; align-items: center"
  63 + >
  64 + 支持.PNG、.JPG、.SVG格式,建议尺寸为1250px × 730px(及以上),大小不超过5M。</div
  65 + >
  66 + </div>
  67 + </Upload>
  68 + </template>
  69 +
  70 + <template #colorInput="{ model, field }"
  71 + ><Input disabled v-model:value="model[field]">
  72 + <template #prefix> <input type="color" v-model="model[field]" /> </template
  73 + ></Input>
  74 + </template>
  75 + </BasicForm>
  76 + </Card>
  77 + <Loading v-bind="compState" />
  78 + <a-button
  79 + @click="handleUpdateInfo"
  80 + size="large"
  81 + type="primary"
  82 + style="margin-top: 20px; background-color: #2950f7; border-radius: 5px"
  83 + >保存信息</a-button
  84 + >
  85 + </div>
  86 +</template>
  87 +
  88 +<script lang="ts">
  89 + import { defineComponent, ref, onMounted, unref } from 'vue';
  90 + import { Card, Upload, Input } from 'ant-design-vue';
  91 + import { BasicForm, useForm } from '/@/components/Form/index';
  92 + import { schemas } from '../config/CVIDraw.config';
  93 + import { Loading } from '/@/components/Loading/index';
  94 + import { useMessage } from '/@/hooks/web/useMessage';
  95 + import type { FileItem } from '/@/components/Upload/src/typing';
  96 + import { logoUpload, iconUpload, bgUpload, getPlatForm, updatePlatForm } from '/@/api/oem/index';
  97 + import { PlusOutlined } from '@ant-design/icons-vue';
  98 + export default defineComponent({
  99 + components: {
  100 + BasicForm,
  101 + Card,
  102 + Loading,
  103 + Upload,
  104 + Input,
  105 + PlusOutlined,
  106 + },
  107 + setup() {
  108 + const compState = ref({
  109 + absolute: false,
  110 + loading: false,
  111 + tip: '拼命加载中...',
  112 + });
  113 + const { createMessage } = useMessage();
  114 + const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
  115 + schemas,
  116 + showSubmitButton: false,
  117 + showResetButton: false,
  118 + labelWidth: 150,
  119 + wrapperCol: {
  120 + span: 10,
  121 + },
  122 + });
  123 +
  124 + const logoPic = ref();
  125 + const iconPic = ref();
  126 + const bgPic = ref();
  127 + // logo图片上传
  128 + async function customUploadLogoPic({ file }) {
  129 + if (beforeUploadLogoPic(file)) {
  130 + const formData = new FormData();
  131 + formData.append('file', file);
  132 + const response = await logoUpload(formData);
  133 + if (response.fileStaticUri) {
  134 + logoPic.value = response.fileStaticUri;
  135 + }
  136 + }
  137 + }
  138 + const beforeUploadLogoPic = (file: FileItem) => {
  139 + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  140 + if (!isJpgOrPng) {
  141 + createMessage.error('只能上传图片文件!');
  142 + }
  143 + const isLt2M = (file.size as number) / 1024 < 500;
  144 + if (!isLt2M) {
  145 + createMessage.error('图片大小不能超过500KB!');
  146 + }
  147 + return isJpgOrPng && isLt2M;
  148 + };
  149 +
  150 + async function customUploadIconPic({ file }) {
  151 + if (beforeUploadIconPic(file)) {
  152 + const formData = new FormData();
  153 + formData.append('file', file);
  154 + const response = await iconUpload(formData);
  155 + if (response.fileStaticUri) {
  156 + iconPic.value = response.fileStaticUri;
  157 + }
  158 + }
  159 + }
  160 + const beforeUploadIconPic = (file: FileItem) => {
  161 + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  162 + if (!isJpgOrPng) {
  163 + createMessage.error('只能上传图片文件!');
  164 + }
  165 + const isLt2M = (file.size as number) / 1024 < 500;
  166 + if (!isLt2M) {
  167 + createMessage.error('图片大小不能超过500KB!');
  168 + }
  169 + return isJpgOrPng && isLt2M;
  170 + };
  171 + // 登录页背景上传
  172 + async function customUploadBgPic({ file }) {
  173 + if (beforeUploadBgPic(file)) {
  174 + const formData = new FormData();
  175 + formData.append('file', file);
  176 + const response = await bgUpload(formData);
  177 + if (response.fileStaticUri) {
  178 + bgPic.value = response.fileStaticUri;
  179 + }
  180 + }
  181 + }
  182 + const beforeUploadBgPic = (file: FileItem) => {
  183 + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  184 + if (!isJpgOrPng) {
  185 + createMessage.error('只能上传图片文件!');
  186 + }
  187 + const isLt2M = (file.size as number) / 1024 / 1024 < 5;
  188 + if (!isLt2M) {
  189 + createMessage.error('图片大小不能超过5MB!');
  190 + }
  191 + return isJpgOrPng && isLt2M;
  192 + };
  193 +
  194 + // 更新
  195 + const handleUpdateInfo = async () => {
  196 + try {
  197 + const fieldValue = getFieldsValue();
  198 + compState.value.loading = true;
  199 + await updatePlatForm({
  200 + ...fieldValue,
  201 + background: unref(bgPic),
  202 + icon: unref(bgPic),
  203 + logo: unref(logoPic),
  204 + });
  205 + compState.value.loading = false;
  206 + createMessage.success('保存信息成功');
  207 + } catch (e) {
  208 + createMessage.error('保存信息失败');
  209 + }
  210 + };
  211 +
  212 + onMounted(async () => {
  213 + const res = await getPlatForm();
  214 + setFieldsValue(res);
  215 + logoPic.value = res.logo;
  216 + iconPic.value = res.icon;
  217 + bgPic.value = res.background;
  218 + });
  219 + return {
  220 + registerForm,
  221 + logoPic,
  222 + iconPic,
  223 + bgPic,
  224 + customUploadLogoPic,
  225 + beforeUploadLogoPic,
  226 + customUploadIconPic,
  227 + beforeUploadIconPic,
  228 + customUploadBgPic,
  229 + beforeUploadBgPic,
  230 + compState,
  231 + handleUpdateInfo,
  232 + };
  233 + },
  234 + });
  235 +</script>
  236 +
  237 +<style lang="less" scoped></style>
  1 +<template>
  2 + <div class="card">
  3 + <Card :bordered="false" class="card"> <BasicForm @register="registerForm" /></Card>
  4 + <Loading v-bind="compState" />
  5 + <a-button
  6 + @click="handleUpdateInfo"
  7 + size="large"
  8 + type="primary"
  9 + style="margin-top: 20px; background-color: #2950f7; border-radius: 5px"
  10 + >更新基本信息</a-button
  11 + >
  12 + </div>
  13 +</template>
  14 +
  15 +<script lang="ts">
  16 + import { defineComponent, onMounted, ref } from 'vue';
  17 + import { Card } from 'ant-design-vue';
  18 + import { BasicForm, useForm } from '/@/components/Form/index';
  19 + import { schemas } from '../config/enterPriseInfo.config';
  20 + import { getEnterPriseDetail, updateEnterPriseDetail } from '/@/api/oem/index';
  21 + import { Loading } from '/@/components/Loading';
  22 + import { useMessage } from '/@/hooks/web/useMessage';
  23 + export default defineComponent({
  24 + components: {
  25 + Card,
  26 + BasicForm,
  27 + Loading,
  28 + },
  29 + setup() {
  30 + const compState = ref({
  31 + absolute: false,
  32 + loading: false,
  33 + tip: '拼命加载中...',
  34 + });
  35 + const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
  36 + labelWidth: 80,
  37 + schemas,
  38 + showResetButton: false,
  39 + showSubmitButton: false,
  40 + wrapperCol: {
  41 + span: 10,
  42 + },
  43 + });
  44 + const { createMessage } = useMessage();
  45 + const handleUpdateInfo = async () => {
  46 + try {
  47 + compState.value.loading = true;
  48 + const fieldsValue = getFieldsValue();
  49 + await updateEnterPriseDetail({
  50 + ...fieldsValue,
  51 + codeProv: fieldsValue.nameProv,
  52 + codeCity: fieldsValue.nameCity,
  53 + codeCoun: fieldsValue.nameCoun,
  54 + codeTown: fieldsValue.nameTown,
  55 + });
  56 + compState.value.loading = false;
  57 + createMessage.success('更新信息成功');
  58 + } catch (e) {
  59 + createMessage.error('更新信息失败');
  60 + }
  61 + };
  62 + onMounted(async () => {
  63 + const res = await getEnterPriseDetail();
  64 + setFieldsValue(res);
  65 + });
  66 +
  67 + return {
  68 + registerForm,
  69 + compState,
  70 + handleUpdateInfo,
  71 + };
  72 + },
  73 + });
  74 +</script>
  75 +
  76 +<style lang="less" scoped></style>
  1 +import type { FormSchema } from '/@/components/Form/index';
  2 +export const schemas: FormSchema[] = [
  3 + {
  4 + field: 'name',
  5 + component: 'Input',
  6 + label: '平台名称',
  7 + colProps: {
  8 + span: 24,
  9 + },
  10 + componentProps: {
  11 + placeholder: '请输入平台名称',
  12 + },
  13 + },
  14 + {
  15 + field: 'logo',
  16 + component: 'Upload',
  17 + label: '平台Logo',
  18 + colProps: {
  19 + span: 24,
  20 + },
  21 + slot: 'logoUpload',
  22 + },
  23 + {
  24 + field: 'background',
  25 + component: 'Input',
  26 + label: '登录页背景图片',
  27 + colProps: {
  28 + span: 24,
  29 + },
  30 + slot: 'bgUpload',
  31 + },
  32 + {
  33 + field: 'backgroundColor',
  34 + component: 'AutoComplete',
  35 + label: '登录页背景颜色',
  36 + colProps: {
  37 + span: 24,
  38 + },
  39 + slot: 'colorInput',
  40 + },
  41 + {
  42 + field: 'background',
  43 + component: 'Input',
  44 + label: '首页轮播图',
  45 + colProps: {
  46 + span: 24,
  47 + },
  48 + slot: 'homeSwiper',
  49 + },
  50 +];
  1 +import type { FormSchema } from '/@/components/Form/index';
  2 +
  3 +export const schemas: FormSchema[] = [
  4 + {
  5 + field: 'name',
  6 + component: 'Input',
  7 + label: '平台名称',
  8 + colProps: {
  9 + span: 24,
  10 + },
  11 + componentProps: {
  12 + placeholder: '请输入平台名称',
  13 + },
  14 + },
  15 + {
  16 + field: 'logo',
  17 + component: 'Upload',
  18 + label: '平台Logo',
  19 + colProps: {
  20 + span: 24,
  21 + },
  22 + slot: 'logoUpload',
  23 + },
  24 + {
  25 + field: 'icon',
  26 + component: 'Upload',
  27 + label: 'Favicon浏览器Icon图标',
  28 + colProps: {
  29 + span: 24,
  30 + },
  31 + slot: 'iconUpload',
  32 + },
  33 + {
  34 + field: 'background',
  35 + component: 'Input',
  36 + label: '登录页背景图片',
  37 + colProps: {
  38 + span: 24,
  39 + },
  40 + slot: 'bgUpload',
  41 + },
  42 + {
  43 + field: 'backgroundColor',
  44 + component: 'AutoComplete',
  45 + label: '登录页背景颜色',
  46 + colProps: {
  47 + span: 24,
  48 + },
  49 + slot: 'colorInput',
  50 + },
  51 + {
  52 + field: 'copyright',
  53 + component: 'Input',
  54 + label: '页面底部版权信息',
  55 + colProps: {
  56 + span: 24,
  57 + },
  58 + },
  59 + {
  60 + field: 'presentedOurselves',
  61 + component: 'Input',
  62 + label: '备案信息',
  63 + colProps: {
  64 + span: 24,
  65 + },
  66 + },
  67 + {
  68 + field: 'domain',
  69 + component: 'Input',
  70 + label: '绑定域名',
  71 + colProps: {
  72 + span: 24,
  73 + },
  74 + },
  75 +];
  1 +import type { FormSchema } from '/@/components/Form/index';
  2 +import { getTownList, getTownChild } from '/@/api/oem/index';
  3 +
  4 +export const schemas: FormSchema[] = [
  5 + {
  6 + field: 'name',
  7 + component: 'Input',
  8 + label: '公司名称',
  9 + colProps: {
  10 + span: 24,
  11 + },
  12 + componentProps: {
  13 + placeholder: '请输入公司名称',
  14 + },
  15 + },
  16 + {
  17 + field: 'abbreviation',
  18 + component: 'Input',
  19 + label: '公司简称',
  20 + colProps: {
  21 + span: 24,
  22 + },
  23 + componentProps: {
  24 + placeholder: '请输入公司简称',
  25 + },
  26 + },
  27 + {
  28 + field: 'officialWebsite',
  29 + component: 'Input',
  30 + label: '公司官网',
  31 + colProps: {
  32 + span: 24,
  33 + },
  34 + componentProps: {
  35 + placeholder: '请输入公司官网',
  36 + },
  37 + },
  38 + {
  39 + field: 'email',
  40 + component: 'Input',
  41 + label: '公司邮箱',
  42 + colProps: {
  43 + span: 24,
  44 + },
  45 +
  46 + componentProps: {
  47 + placeholder: '请输入公司邮箱',
  48 + },
  49 + },
  50 + {
  51 + field: 'synopsis',
  52 + component: 'InputTextArea',
  53 + label: '公司简介',
  54 + colProps: {
  55 + span: 24,
  56 + },
  57 + componentProps: {
  58 + placeholder: '请输入公司简介',
  59 + autoSize: { minRows: 8, maxRows: 12 },
  60 + showCount: true,
  61 + },
  62 + },
  63 + {
  64 + field: 'country',
  65 + component: 'Select',
  66 + label: '国家/地区',
  67 + colProps: {
  68 + span: 24,
  69 + },
  70 + defaultValue: '1',
  71 + componentProps: {
  72 + placeholder: '请选择国家/地区',
  73 + options: [
  74 + {
  75 + label: '中国',
  76 + value: '1',
  77 + },
  78 + ],
  79 + },
  80 + },
  81 + {
  82 + field: 'nameProv',
  83 + component: 'ApiSelect',
  84 + label: '所在城市',
  85 + colProps: {
  86 + span: 5,
  87 + },
  88 + componentProps: ({ formModel, formActionType }) => {
  89 + return {
  90 + api: getTownList,
  91 + labelField: 'nameProv',
  92 + valueField: 'codeProv',
  93 + placeholder: '请选择省份',
  94 + async onChange(value) {
  95 + let nameCity = await getTownChild('codeProv', value);
  96 + nameCity.forEach((item) => {
  97 + item.label = item.nameCity;
  98 + item.value = item.codeCity;
  99 + });
  100 + const { updateSchema } = formActionType;
  101 + if (value === undefined) {
  102 + formModel.nameCity = undefined; // reset city value
  103 + formModel.nameCoun = undefined;
  104 + formModel.nameTown = undefined;
  105 + nameCity = [];
  106 + updateSchema({
  107 + field: 'nameCoun',
  108 + componentProps: {
  109 + options: [],
  110 + },
  111 + });
  112 + updateSchema({
  113 + field: 'nameTown',
  114 + componentProps: {
  115 + options: [],
  116 + },
  117 + });
  118 + }
  119 + updateSchema({
  120 + field: 'nameCity',
  121 + componentProps: () => {
  122 + return {
  123 + options: nameCity,
  124 + placeholder: '请选择城市',
  125 + async onChange(value) {
  126 + // 获取区数据
  127 + let nameCoun = await getTownChild('codeCity', value);
  128 + nameCoun.forEach((item) => {
  129 + item.label = item.nameCoun;
  130 + item.value = item.codeCoun;
  131 + });
  132 + if (value === undefined) {
  133 + formModel.nameCoun = undefined; // reset city value
  134 + formModel.nameTown = undefined;
  135 + nameCoun = [];
  136 + updateSchema({
  137 + field: 'nameTown',
  138 + componentProps: {
  139 + options: [],
  140 + },
  141 + });
  142 + }
  143 + updateSchema({
  144 + field: 'nameCoun',
  145 + componentProps: {
  146 + // 请选择区
  147 + options: nameCoun,
  148 + async onChange(value) {
  149 + let nameTown = await getTownChild('codeCoun', value);
  150 + nameTown.forEach((item) => {
  151 + item.label = item.nameTown;
  152 + item.value = item.codeTown;
  153 + });
  154 + if (value === undefined) {
  155 + formModel.nameTown = undefined;
  156 + nameTown = [];
  157 + }
  158 + updateSchema({
  159 + field: 'nameTown',
  160 + componentProps: {
  161 + placeholder: '请选择街道/城镇',
  162 + options: nameTown,
  163 + },
  164 + });
  165 + },
  166 + },
  167 + });
  168 + },
  169 + };
  170 + },
  171 + });
  172 + },
  173 + };
  174 + },
  175 + },
  176 + {
  177 + field: 'nameCity',
  178 + component: 'Select',
  179 + label: '',
  180 +
  181 + colProps: {
  182 + span: 5,
  183 + style: {
  184 + marginLeft: '-80px',
  185 + },
  186 + },
  187 + },
  188 + {
  189 + field: 'nameCoun',
  190 + component: 'Select',
  191 + label: '',
  192 + colProps: {
  193 + span: 5,
  194 + style: {
  195 + marginLeft: '-160px',
  196 + },
  197 + },
  198 + componentProps: {
  199 + placeholder: '请选择区/县',
  200 + },
  201 + },
  202 + {
  203 + field: 'nameTown',
  204 + component: 'Select',
  205 + label: '',
  206 + colProps: {
  207 + span: 6,
  208 + style: {
  209 + marginLeft: '-160px',
  210 + },
  211 + },
  212 + componentProps: {
  213 + placeholder: '请选择街道/城镇',
  214 + },
  215 + },
  216 +
  217 + // {
  218 + // field: 'nameProv',
  219 + // label: '所在城市',
  220 + // component: 'Cascader',
  221 + // },
  222 +
  223 + {
  224 + field: 'address',
  225 + component: 'Input',
  226 + label: '详细地址',
  227 + colProps: {
  228 + span: 24,
  229 + },
  230 + componentProps: {
  231 + placeholder: '请输入详细地址',
  232 + },
  233 + },
  234 +
  235 + {
  236 + field: 'contacts',
  237 + component: 'Input',
  238 + label: '联系人',
  239 + colProps: {
  240 + span: 24,
  241 + },
  242 + componentProps: {
  243 + placeholder: '请输入联系人',
  244 + },
  245 + },
  246 + {
  247 + field: 'tel',
  248 + component: 'Input',
  249 + label: '联系电话',
  250 + colProps: {
  251 + span: 24,
  252 + },
  253 + componentProps: {
  254 + placeholder: '请输入联系电话',
  255 + },
  256 + },
  257 +];
  1 +<template>
  2 + <div class="platform flex">
  3 + <Card class="tab-card" :bordered="false">
  4 + <Tabs v-model:activeKey="activeKey" tab-position="left">
  5 + <TabPane key="企业信息" tab="企业信息" />
  6 + <TabPane key="平台定制" tab="平台定制" />
  7 + <TabPane key="APP定制" tab="APP定制" />
  8 + </Tabs>
  9 + </Card>
  10 +
  11 + <div style="width: 100%">
  12 + <div class="title">{{ activeKey }}</div>
  13 + <EnterpriseInfo v-if="activeKey === '企业信息'" />
  14 + <CVIDraw v-else-if="activeKey === '平台定制'" />
  15 + <AppDraw v-else />
  16 + </div>
  17 + </div>
  18 +</template>
  19 +
  20 +<script lang="ts" setup>
  21 + import { Tabs, TabPane, Card } from 'ant-design-vue';
  22 + import { ref } from 'vue';
  23 + import EnterpriseInfo from './components/EnterpriseInfo.vue';
  24 + import CVIDraw from './components/CVIDraw.vue';
  25 + import AppDraw from './components/AppDraw.vue';
  26 + const activeKey = ref('APP定制');
  27 +</script>
  28 +
  29 +<style lang="less" scoped>
  30 + .title {
  31 + width: 97.4%;
  32 + height: 50px;
  33 + margin: 20px;
  34 + line-height: 50px;
  35 + font-size: 18px;
  36 + border-radius: 8px;
  37 + background-color: #fff;
  38 + padding-left: 10px;
  39 + }
  40 + .tab-card {
  41 + margin: 20px 0 20px 20px;
  42 + border-radius: 5px;
  43 + }
  44 + .card {
  45 + margin: 20px;
  46 + border-radius: 8px;
  47 + }
  48 +</style>
  1 +export interface FileItem {
  2 + uid: string;
  3 + name?: string;
  4 + status?: string;
  5 + response?: string;
  6 + percent?: number;
  7 + url?: string;
  8 + preview?: string;
  9 + originFileObj?: any;
  10 +}
  11 +
  12 +export interface FileInfo {
  13 + file: FileItem;
  14 + fileList: FileItem[];
  15 +}