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 15
16 16 // 获取
17 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 48 addAlarmContact(params);
49 49 };
50 50
  51 +// 查询设备分页数据
51 52 export const devicePage = (params: DeviceQueryParam) => {
52 53 return defHttp.get<DeviceModel>({
53 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 196 export const defHttp = createAxios();
197 197
198 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 33 'https://api.map.baidu.com/getscript?v=3.0&ak=7uOPPyAHn2Y2ZryeQqHtcRqtIY374vKa';
34 34 const wrapRef = ref<HTMLDivElement | null>(null);
35 35 const { toPromise } = useScript({ src: BAI_DU_MAP_URL });
  36 +
36 37 async function initMap() {
37 38 await toPromise();
38 39 await nextTick();
... ...
1 1 <template>
2 2 <div class="md:flex">
3 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 40 </div>
25   - </Card>
  41 + <div class="growCardItem-bottom"> 今日新增 {{ item.newDay }}</div>
  42 + </div>
26 43 </template>
27 44 </div>
28 45 </template>
29 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 47 import { growCardList } from '../data';
34 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 5 :active-tab-key="activeKey"
6 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 20 <VisitAnalysis />
10   - </p>
11   - <p v-if="activeKey === 'tab2'">
  21 + </div>
  22 + <div v-else>
  23 + <p class="center">消息数</p>
12 24 <VisitAnalysisBar />
13   - </p>
  25 + </div>
14 26 </Card>
15 27 </template>
16 28 <script lang="ts" setup>
17 29 import { ref } from 'vue';
18   - import { Card } from 'ant-design-vue';
  30 + import { Card, DatePicker } from 'ant-design-vue';
19 31 import VisitAnalysis from './VisitAnalysis.vue';
20 32 import VisitAnalysisBar from './VisitAnalysisBar.vue';
21 33
... ... @@ -24,15 +36,43 @@
24 36 const tabListTitle = [
25 37 {
26 38 key: 'tab1',
27   - tab: '流量趋势',
  39 + tab: '告警数统计',
28 40 },
29 41 {
30 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 48 function onTabChange(key) {
36 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 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 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 1 export interface GrowCardItem {
2   - icon: string;
  2 + imgUrl: string;
3 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 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 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 14 </div>
10 15 </div>
11 16 </template>
... ... @@ -13,12 +18,9 @@
13 18 import { ref } from 'vue';
14 19 import GrowCard from './components/GrowCard.vue';
15 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 23 const loading = ref(true);
21   -
22 24 setTimeout(() => {
23 25 loading.value = false;
24 26 }, 1500);
... ...
1 1 <template>
2 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 5 <template #action>
10   - <div @click="handleDeleteAlarm(index)" class="cursor-pointer">
  6 + <div @click="deleteAlarmRule(index)" class="cursor-pointer">
11 7 <DeleteOutlined style="font-size: 20px" class="mr-2" />
12 8 </div>
13 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 11 <CollapseContainer>
21 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 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 25 <p style="color: #f5594e" class="mt-4 ml-4"
42 26 >请添加报警规则条件
43 27 <PlusOutlined class="cursor-pointer ml-4" style="font-size: 20px"
... ... @@ -47,68 +31,29 @@
47 31 <EditOutlined class="cursor-pointer ml-4" style="font-size: 20px"
48 32 /></p>
49 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 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 37 </div>
58 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 41 <MinusCircleOutlined
62   - style="font-size: 25px color:#305680"
  42 + style="font-size: 25px; color: #305680"
63 43 class="cursor-pointer"
64   - @click="handleDeleteCondition(index, index1)"
  44 + @click="deleteCondition(index, createIndex)"
65 45 />
66   - </a-tooltip>
  46 + </Tooltip>
67 47 </div>
68 48 </div>
69 49 </template>
70 50 <a-button class="mt-5" @click="addCreateRole(index)"
71 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 57 </CollapseContainer>
113 58 </template>
114 59 <div class="flex justify-start">
... ... @@ -117,163 +62,156 @@
117 62 </div>
118 63 </div>
119 64 </template>
  65 +
120 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 72 import {
135 73 DeleteOutlined,
136 74 MinusCircleOutlined,
137 75 PlusCircleOutlined,
138 76 PlusOutlined,
139 77 EditOutlined,
  78 + CloseOutlined,
140 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 81 export default defineComponent({
150 82 components: {
  83 + BasicForm,
  84 + CollapseContainer,
151 85 DeleteOutlined,
152 86 MinusCircleOutlined,
153 87 PlusCircleOutlined,
154   - CollapseContainer,
155   - EditOutlined,
156 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 94 emits: ['prev'],
171 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 103 const prevStep = () => {
208 104 emit('prev');
209 105 };
210   - // 添加报警规则
211 106 const addAlarmRule = () => {
212   - alarmList.value.push({
  107 + unref(alarmList).push({
  108 + id: Date.now(),
213 109 alarmType: '',
214 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 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 181 return {
247   - rules,
248 182 alarmList,
249   - options,
250   - visible,
  183 + deleteAlarmRule,
251 184 prevStep,
252 185 addAlarmRule,
  186 +
  187 + registerForm,
  188 +
  189 + registerFormHighSetting,
  190 +
  191 + registerFormCreateAlarm,
253 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 199 </script>
  200 +
265 201 <style lang="less" scoped>
266 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 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 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 46 export const step3Schemas: FormSchema[] = [
47 47 {
48 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 79 component: 'Select',
50   - label: '报警规则',
51   - defaultValue: 'DEFAULT',
  80 + label: '严重程度',
52 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 +}
... ...