|
@@ -0,0 +1,588 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { inject, onMounted, ref } from 'vue';
|
|
|
+import { ColorPicker } from 'vue-color-kit';
|
|
|
+import { message } from 'ant-design-vue';
|
|
|
+
|
|
|
+import SvgIcon from '@/components/SvgIcon.vue';
|
|
|
+import { useDictData } from '@/hooks/dict-data';
|
|
|
+import { useRequest } from '@/hooks/request';
|
|
|
+import { t } from '@/i18n';
|
|
|
+import { addOrganization, addUploadLogo, getInfoListByOrgId, groupList, updateOrganization } from '@/api';
|
|
|
+import { DictCode } from '@/constants';
|
|
|
+import { SET_COLOR_PRIMARY } from '@/constants/inject-key';
|
|
|
+
|
|
|
+import EquipmentLimitations from './EquipmentLimitations.vue';
|
|
|
+
|
|
|
+import type {
|
|
|
+ CreateCustomer,
|
|
|
+ EquipmentLimitationsItem,
|
|
|
+ EquipmentTypeItem,
|
|
|
+ OrgDeviceLimit,
|
|
|
+ UseGuideStepItemExpose,
|
|
|
+ UseGuideStepItemProps,
|
|
|
+} from '@/types';
|
|
|
+
|
|
|
+import 'vue-color-kit/dist/vue-color-kit.css';
|
|
|
+
|
|
|
+// 引入样式文件
|
|
|
+interface ColorStyle {
|
|
|
+ background: string;
|
|
|
+ border: string;
|
|
|
+ index: number;
|
|
|
+ color: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface NewColor {
|
|
|
+ hex: string;
|
|
|
+}
|
|
|
+
|
|
|
+const { dictData: dataValidityPeriod, getDictData: getDataValidityPeriod } = useDictData(DictCode.DataValidityPeriod);
|
|
|
+const equipmentLimitationsRefs = ref<InstanceType<typeof EquipmentLimitations>[]>([]);
|
|
|
+const equipmentLimitationsList = ref<EquipmentLimitationsItem[]>([
|
|
|
+ {
|
|
|
+ deviceGlobalId: undefined,
|
|
|
+ upperLimit: 0,
|
|
|
+ },
|
|
|
+]);
|
|
|
+const orgDeviceLimits = ref<OrgDeviceLimit[]>([]);
|
|
|
+const setColorPrimary = inject(SET_COLOR_PRIMARY, undefined);
|
|
|
+const fileList = ref([]);
|
|
|
+const fileImg = ref<Blob>();
|
|
|
+const customizationColor = ref<boolean>(false);
|
|
|
+const { handleRequest } = useRequest();
|
|
|
+const equipmentType = ref<EquipmentTypeItem[]>([]);
|
|
|
+
|
|
|
+const props = defineProps<UseGuideStepItemProps<CreateCustomer>>();
|
|
|
+const colorStyle: ColorStyle[] = [
|
|
|
+ {
|
|
|
+ background: 'background:#32bac0',
|
|
|
+ border: 'border:1px solid #32bac0',
|
|
|
+ color: '#32bac0',
|
|
|
+ index: 0,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ background: 'background:#256AFE',
|
|
|
+ border: 'border:1px solid #256AFE',
|
|
|
+ color: '#256AFE',
|
|
|
+ index: 1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ background: 'background:#00A94D',
|
|
|
+ border: 'border:1px solid #00A94D',
|
|
|
+ color: '#00A94D',
|
|
|
+ index: 2,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ background: 'background:#7866FF',
|
|
|
+ border: 'border:1px solid #7866FF',
|
|
|
+ color: '#7866FF',
|
|
|
+ index: 3,
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+// 预定义颜色组
|
|
|
+const presetColors = ref<string[]>([
|
|
|
+ '#000000',
|
|
|
+ '#FFFFFF',
|
|
|
+ '#FF1900',
|
|
|
+ '#F47365',
|
|
|
+ '#FFB243',
|
|
|
+ '#FFE623',
|
|
|
+ '#6EFF2A',
|
|
|
+ '#1BC7B1',
|
|
|
+ '#00BEFF',
|
|
|
+ '#2E81FF',
|
|
|
+ '#5D61FF',
|
|
|
+ '#FF89CF',
|
|
|
+ '#FC3CAD',
|
|
|
+ '#BF3DCE',
|
|
|
+ '#8E00A7',
|
|
|
+]);
|
|
|
+
|
|
|
+// 颜色变化回调函数
|
|
|
+const handleColorChange = (newColor: NewColor) => {
|
|
|
+ props.form.selectedColor = newColor.hex;
|
|
|
+ setColorPrimary?.(props.form.selectedColor);
|
|
|
+ props.form.themeColor = props.form.selectedColor;
|
|
|
+};
|
|
|
+const addCustomizationColor = () => {
|
|
|
+ customizationColor.value = !customizationColor.value;
|
|
|
+};
|
|
|
+const colorClick = (color: string) => {
|
|
|
+ setColorPrimary?.(color);
|
|
|
+ props.form.themeColor = color;
|
|
|
+};
|
|
|
+
|
|
|
+const finish = async () => {
|
|
|
+ orgDeviceLimits.value = [];
|
|
|
+ // eslint-disable-next-line no-useless-catch
|
|
|
+ try {
|
|
|
+ // 创建校验任务队列(包含所有子组件+父组件自身)
|
|
|
+ const allTasks = [...equipmentLimitationsRefs.value.map((c) => c.formRefSubmit())];
|
|
|
+ // 并行执行所有校验(父+子)
|
|
|
+ await Promise.all(allTasks);
|
|
|
+
|
|
|
+ equipmentLimitationsList.value.forEach((item) => {
|
|
|
+ orgDeviceLimits.value.push({
|
|
|
+ deviceGlobalId: item.deviceGlobalId,
|
|
|
+ upperLimit: item.upperLimit,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ orgDeviceLimits.value.push({
|
|
|
+ deviceGlobalId: -1,
|
|
|
+ upperLimit: props.form.stationsNumber,
|
|
|
+ });
|
|
|
+ if (props.form.id) {
|
|
|
+ updateOrganization({
|
|
|
+ orgName: props.form.orgName,
|
|
|
+ logo: props.form.logo,
|
|
|
+ themeColor: props.form.themeColor,
|
|
|
+ dataValidityPeriod: props.form.dataValidityPeriod,
|
|
|
+ enabled: '1',
|
|
|
+ id: props.form.id,
|
|
|
+ orgDeviceLimits: orgDeviceLimits.value,
|
|
|
+ startTenancy: props.form.leaseTerm[0].format('YYYY-MM-DD'),
|
|
|
+ endTenancy: props.form.leaseTerm[1].format('YYYY-MM-DD'),
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ props.form.id = await addOrganization({
|
|
|
+ orgName: props.form.orgName,
|
|
|
+ logo: props.form.logo,
|
|
|
+ themeColor: props.form.themeColor,
|
|
|
+ dataValidityPeriod: props.form.dataValidityPeriod,
|
|
|
+ enabled: '1',
|
|
|
+ orgDeviceLimits: orgDeviceLimits.value,
|
|
|
+ startTenancy: props.form.leaseTerm[0].format('YYYY-MM-DD'),
|
|
|
+ endTenancy: props.form.leaseTerm[1].format('YYYY-MM-DD'),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
+};
|
|
|
+const beforeUpload = (file: Blob) => {
|
|
|
+ fileImg.value = file;
|
|
|
+ const isJpgOrPng = file.type === 'image/jpg' || file.type === 'image/png' || file.type === 'image/jpeg';
|
|
|
+ console.log(isJpgOrPng);
|
|
|
+ if (!isJpgOrPng) {
|
|
|
+ message.error('请上传JPG/PNG/JPEG格式的图片!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const addClick = () => {
|
|
|
+ equipmentLimitationsList.value.push({
|
|
|
+ deviceGlobalId: undefined,
|
|
|
+ upperLimit: 0,
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const deleteClick = (index: number) => {
|
|
|
+ equipmentLimitationsList.value.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+const handleChange = () => {
|
|
|
+ if (fileImg.value) {
|
|
|
+ // 上传接口
|
|
|
+ handleRequest(async () => {
|
|
|
+ const { fileName } = await addUploadLogo(fileImg.value as Blob);
|
|
|
+ props.form.imageUrl = URL.createObjectURL(fileImg.value as Blob);
|
|
|
+ props.form.logo = fileName;
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+defineExpose<UseGuideStepItemExpose>({
|
|
|
+ finish,
|
|
|
+});
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ handleRequest(async () => {
|
|
|
+ await getDataValidityPeriod();
|
|
|
+ equipmentType.value = await groupList({
|
|
|
+ dataType: 1,
|
|
|
+ });
|
|
|
+ if (props.form.id) {
|
|
|
+ equipmentLimitationsList.value = [];
|
|
|
+ const data = await getInfoListByOrgId(props.form.id);
|
|
|
+ data.forEach((item) => {
|
|
|
+ if (item.deviceGlobalId !== -1) {
|
|
|
+ equipmentLimitationsList.value.push({
|
|
|
+ deviceGlobalId: item.deviceGlobalId,
|
|
|
+ upperLimit: item.upperLimit,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="organization text">
|
|
|
+ <AFormItem label="组织名称" name="orgName">
|
|
|
+ <AInput v-model:value="form.orgName" class="organization-input" :placeholder="$t('common.pleaseEnter')" />
|
|
|
+ </AFormItem>
|
|
|
+ <AFormItem label="租期" name="leaseTerm">
|
|
|
+ <ARangePicker
|
|
|
+ class="organization-input"
|
|
|
+ v-model:value="form.leaseTerm"
|
|
|
+ :allow-clear="false"
|
|
|
+ :separator="$t('common.to')"
|
|
|
+ />
|
|
|
+ </AFormItem>
|
|
|
+ <div class="upper-limit">设置数量上限</div>
|
|
|
+ <AFormItem label="站房数(设备组)" name="stationsNumber">
|
|
|
+ <AInputNumber
|
|
|
+ :placeholder="t('common.pleaseEnter')"
|
|
|
+ v-model:value="form.stationsNumber"
|
|
|
+ class="organization-input"
|
|
|
+ addon-after="个"
|
|
|
+ />
|
|
|
+ </AFormItem>
|
|
|
+ <div v-for="(item, index) in equipmentLimitationsList" :key="index">
|
|
|
+ <EquipmentLimitations
|
|
|
+ ref="equipmentLimitationsRefs"
|
|
|
+ :index="index"
|
|
|
+ :form="item"
|
|
|
+ :equipment-type="equipmentType"
|
|
|
+ @addClick="addClick"
|
|
|
+ @deleteClick="deleteClick"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <AFormItem label="数据存储时长" name="dataValidityPeriod">
|
|
|
+ <ASelect
|
|
|
+ class="organization-input"
|
|
|
+ v-model:value="form.dataValidityPeriod"
|
|
|
+ :options="dataValidityPeriod"
|
|
|
+ :field-names="{ label: 'dictValue', value: 'dictEngValue' }"
|
|
|
+ :placeholder="$t('common.plzSelect')"
|
|
|
+ />
|
|
|
+ </AFormItem>
|
|
|
+
|
|
|
+ <AFormItem label="Logo">
|
|
|
+ <div class="upload-dev">
|
|
|
+ <AUpload
|
|
|
+ v-model:file-list="fileList"
|
|
|
+ name="file"
|
|
|
+ list-type="picture-card"
|
|
|
+ class="avatar-uploader"
|
|
|
+ action=""
|
|
|
+ :show-upload-list="false"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ :custom-request="handleChange"
|
|
|
+ >
|
|
|
+ <template v-if="form.imageUrl">
|
|
|
+ <div class="img-dev">
|
|
|
+ <img class="img-style" :src="form.imageUrl" alt="avatar" />
|
|
|
+ <div class="background-style"></div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div v-else>
|
|
|
+ <SvgIcon class="icon-size" name="upload-pictures" />
|
|
|
+ <div class="text">上传图片</div>
|
|
|
+ </div>
|
|
|
+ </AUpload>
|
|
|
+ <div class="format-text">支持JPG/PNG/JPEG格式的图片</div>
|
|
|
+ </div>
|
|
|
+ </AFormItem>
|
|
|
+
|
|
|
+ <AFlex>
|
|
|
+ <AFlex align="center">
|
|
|
+ <div class="text">主题色</div>
|
|
|
+ <AFlex class="color-list">
|
|
|
+ <div v-for="(item, index) in colorStyle" :key="index">
|
|
|
+ <div @click="colorClick(item.color)">
|
|
|
+ <AFlex
|
|
|
+ justify="center"
|
|
|
+ align="center"
|
|
|
+ :style="form.themeColor === item.color ? item.border : 'border:1px solid #fff;'"
|
|
|
+ class="color-div"
|
|
|
+ >
|
|
|
+ <div :style="item.background" class="color-style"></div>
|
|
|
+ </AFlex>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <AFlex class="flex-relative">
|
|
|
+ <div @click="addCustomizationColor">
|
|
|
+ <AFlex class="color-customization" justify="space-between" align="center">
|
|
|
+ <div class="customization-style" :style="'background:' + form.selectedColor"></div>
|
|
|
+ <div class="customization-text">{{ form.selectedColor }}</div>
|
|
|
+ </AFlex>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="color-selection">
|
|
|
+ <ColorPicker
|
|
|
+ v-show="customizationColor"
|
|
|
+ v-model:color="form.selectedColor"
|
|
|
+ :colors-default="presetColors"
|
|
|
+ theme="dark"
|
|
|
+ @changeColor="handleColorChange"
|
|
|
+ style="width: 220px"
|
|
|
+ class="picker-color"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </AFlex>
|
|
|
+ </AFlex>
|
|
|
+ </AFlex>
|
|
|
+ </AFlex>
|
|
|
+ <div class="display-effect">
|
|
|
+ <div class="display-effect-top">
|
|
|
+ <span class="desktop-header-dot" v-for="i in 3" :key="i"></span>
|
|
|
+ </div>
|
|
|
+ <AFlex class="desktop-content" wrap="wrap" justify="space-between">
|
|
|
+ <div class="desktop-content-div" v-for="i in 6" :key="i">
|
|
|
+ <AFlex>
|
|
|
+ <div class="desktop-content-top-left"></div>
|
|
|
+ <AFlex :vertical="true">
|
|
|
+ <div class="desktop-content-top-right"></div>
|
|
|
+ <div class="desktop-content-top-right-div"></div>
|
|
|
+ </AFlex>
|
|
|
+ </AFlex>
|
|
|
+ <div class="desktop-content-bottom"></div>
|
|
|
+ <div class="desktop-content-bottom-div"></div>
|
|
|
+ </div>
|
|
|
+ </AFlex>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.upper-limit {
|
|
|
+ margin-top: 40px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 24px;
|
|
|
+ color: #333;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.background-style {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ z-index: 99999;
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: rgb(0 0 0 / 50%);
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.img-dev {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.img-style {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.color-selection {
|
|
|
+ position: absolute;
|
|
|
+ top: -270px;
|
|
|
+ left: 80px;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-color {
|
|
|
+ :deep(.color-alpha) {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.color-type) {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.hue) {
|
|
|
+ margin-left: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.customization-text {
|
|
|
+ font-size: 12px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 20px;
|
|
|
+ color: #999;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.customization-style {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.flex-relative {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.color-customization {
|
|
|
+ width: 90px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ border: 1px solid #d6d6d6;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content {
|
|
|
+ position: relative;
|
|
|
+ top: -30px;
|
|
|
+ left: 16px;
|
|
|
+ width: 288px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content-bottom-div {
|
|
|
+ width: 48px;
|
|
|
+ height: 3px;
|
|
|
+ background: #d8d8d8;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content-bottom {
|
|
|
+ width: 71px;
|
|
|
+ height: 3px;
|
|
|
+ margin: 6px 0;
|
|
|
+ background: #d8d8d8;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content-top-right-div {
|
|
|
+ width: 26px;
|
|
|
+ height: 3px;
|
|
|
+ background: #d8d8d8;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content-top-right {
|
|
|
+ width: 40px;
|
|
|
+ height: 3px;
|
|
|
+ margin: 6px 0;
|
|
|
+ background: #d8d8d8;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content-top-left {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ margin-right: 8px;
|
|
|
+ background: var(--antd-color-primary-opacity-50);
|
|
|
+ border: 1px solid var(--antd-color-primary);
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-content-div {
|
|
|
+ width: 88px;
|
|
|
+ height: 57px;
|
|
|
+ padding: 6px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #d9dbe2;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.display-effect-top {
|
|
|
+ display: flex;
|
|
|
+ width: 319px;
|
|
|
+ height: 94px;
|
|
|
+ padding-top: 9px;
|
|
|
+ padding-left: 16px;
|
|
|
+ background-color: var(--antd-color-primary);
|
|
|
+ border-top-left-radius: 8px;
|
|
|
+ border-top-right-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.display-effect {
|
|
|
+ width: 320px;
|
|
|
+ height: 206px;
|
|
|
+ margin-top: 24px;
|
|
|
+ margin-left: 138px;
|
|
|
+ background: #fafafa;
|
|
|
+ border: 1px solid var(--antd-color-primary);
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.desktop-header-dot {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ background-color: white;
|
|
|
+ border-radius: 50%;
|
|
|
+
|
|
|
+ & + & {
|
|
|
+ margin-left: 6px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.color-list {
|
|
|
+ margin-left: 95px;
|
|
|
+}
|
|
|
+
|
|
|
+.color-style {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.color-div {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ margin-right: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-dev {
|
|
|
+ margin-top: 8px;
|
|
|
+ margin-left: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.icon-size {
|
|
|
+ font-size: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.format-text {
|
|
|
+ font-size: 12px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 20px;
|
|
|
+ color: #999;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.organization-input {
|
|
|
+ width: 256px;
|
|
|
+ height: 32px;
|
|
|
+ margin-left: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ color: rgb(0 0 0 / 65%);
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.organization-input) {
|
|
|
+ .ant-input-number-group .ant-input-number-group-addon {
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|