Browse Source

fixed cropper

pull/709/head
cKey 3 years ago
parent
commit
6a1ce12739
  1. 150
      apps/vue/src/components/Cropper/src/CopperModal.vue
  2. 224
      apps/vue/src/components/Cropper/src/Cropper.vue
  3. 112
      apps/vue/src/components/Cropper/src/CropperAvatar.vue
  4. 146
      apps/vue/src/components/registerGlobComp.ts
  5. 3
      apps/vue/src/main.ts

150
apps/vue/src/components/Cropper/src/CopperModal.vue

@ -22,14 +22,14 @@
</div> </div>
<div :class="`${prefixCls}-toolbar`"> <div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload"> <Upload :fileList="fileList" accept="image/*" :beforeUpload="handleBeforeUpload">
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom"> <Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" /> <Button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Tooltip> </Tooltip>
</Upload> </Upload>
<Space> <Space>
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:reload-outlined" preIcon="ant-design:reload-outlined"
size="small" size="small"
@ -38,7 +38,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:rotate-left-outlined" preIcon="ant-design:rotate-left-outlined"
size="small" size="small"
@ -47,7 +47,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:rotate-right-outlined" preIcon="ant-design:rotate-right-outlined"
size="small" size="small"
@ -56,7 +56,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="vaadin:arrows-long-h" preIcon="vaadin:arrows-long-h"
size="small" size="small"
@ -65,7 +65,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="vaadin:arrows-long-v" preIcon="vaadin:arrows-long-v"
size="small" size="small"
@ -74,7 +74,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:zoom-in-outlined" preIcon="ant-design:zoom-in-outlined"
size="small" size="small"
@ -83,7 +83,7 @@
/> />
</Tooltip> </Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom"> <Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
<a-button <Button
type="primary" type="primary"
preIcon="ant-design:zoom-out-outlined" preIcon="ant-design:zoom-out-outlined"
size="small" size="small"
@ -110,13 +110,15 @@
</div> </div>
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import type { CropendResult, Cropper } from './typing'; import type { CropendResult, Cropper } from './typing';
import type { UploadProps } from 'ant-design-vue';
import { defineComponent, ref } from 'vue'; import { ref } from 'vue';
import CropperImage from './Cropper.vue'; import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue'; import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { Button } from '/@/components/Button';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver'; import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
@ -124,90 +126,70 @@
type apiFunParams = { file: Blob; name: string; filename: string }; type apiFunParams = { file: Blob; name: string; filename: string };
const props = { const emits = defineEmits(['uploadSuccess', 'register']);
const props = defineProps({
circled: { type: Boolean, default: true }, circled: { type: Boolean, default: true },
uploadApi: { uploadApi: {
type: Function as PropType<(params: apiFunParams) => Promise<any>>, type: Function as PropType<(params: apiFunParams) => Promise<any>>,
}, },
}; });
let filename = '';
export default defineComponent({ const src = ref('');
name: 'CropperModal', const previewSource = ref('');
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip }, const cropper = ref<Cropper>();
props, const fileList = ref<UploadProps['fileList']>([]);
emits: ['uploadSuccess', 'register'], let scaleX = 1;
setup(props, { emit }) { let scaleY = 1;
let filename = '';
const src = ref('');
const previewSource = ref('');
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { prefixCls } = useDesign('cropper-am'); const { prefixCls } = useDesign('cropper-am');
const [register, { closeModal, setModalProps }] = useModalInner(); const [register, { closeModal, setModalProps }] = useModalInner();
const { t } = useI18n(); const { t } = useI18n();
// Block upload // Block upload
function handleBeforeUpload(file: File) { function handleBeforeUpload(file: File) {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
src.value = ''; src.value = '';
previewSource.value = ''; previewSource.value = '';
reader.onload = function (e) { reader.onload = function (e) {
src.value = (e.target?.result as string) ?? ''; src.value = (e.target?.result as string) ?? '';
filename = file.name; filename = file.name;
}; };
return false; return false;
} }
function handleCropend({ imgBase64 }: CropendResult) { function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64; previewSource.value = imgBase64;
} }
function handleReady(cropperInstance: Cropper) { function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance; cropper.value = cropperInstance;
} }
function handlerToolbar(event: string, arg?: number) { function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') { if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1; scaleX = arg = scaleX === -1 ? 1 : -1;
} }
if (event === 'scaleY') { if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1; scaleY = arg = scaleY === -1 ? 1 : -1;
} }
cropper?.value?.[event]?.(arg); cropper?.value?.[event]?.(arg);
} }
async function handleOk() { async function handleOk() {
const uploadApi = props.uploadApi; const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) { if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value); const blob = dataURLtoBlob(previewSource.value);
try { try {
setModalProps({ confirmLoading: true }); setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename }); const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.data }); emits('uploadSuccess', { source: previewSource.value, data: result.data });
closeModal(); closeModal();
} finally { } finally {
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
}
}
} }
}
return { }
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script> </script>
<style lang="less"> <style lang="less">

224
apps/vue/src/components/Cropper/src/Cropper.vue

@ -10,9 +10,9 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { defineComponent, onMounted, ref, unref, computed, onUnmounted } from 'vue'; import { onMounted, ref, unref, computed, onUnmounted, useAttrs } from 'vue';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css'; import 'cropperjs/dist/cropper.css';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
@ -20,6 +20,7 @@
type Options = Cropper.Options; type Options = Cropper.Options;
const emits = defineEmits(['cropend', 'ready', 'cropendError']);
const defaultOptions: Options = { const defaultOptions: Options = {
aspectRatio: 1, aspectRatio: 1,
zoomable: true, zoomable: true,
@ -43,7 +44,7 @@
rotatable: true, rotatable: true,
}; };
const props = { const props = defineProps({
src: { type: String, required: true }, src: { type: String, required: true },
alt: { type: String }, alt: { type: String },
circled: { type: Boolean, default: false }, circled: { type: Boolean, default: false },
@ -55,124 +56,117 @@
}, },
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) }, imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) }, options: { type: Object as PropType<Options>, default: () => ({}) },
}; });
export default defineComponent({ const attrs = useAttrs();
name: 'CropperImage',
props,
emits: ['cropend', 'ready', 'cropendError'],
setup(props, { attrs, emit }) {
const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Nullable<Cropper>>();
const isReady = ref(false);
const { prefixCls } = useDesign('cropper-image');
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
const getImageStyle = computed((): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle,
};
});
const getClass = computed(() => {
return [
prefixCls,
attrs.class,
{
[`${prefixCls}--circled`]: props.circled,
},
];
});
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, '') + 'px' };
});
onMounted(init);
onUnmounted(() => {
cropper.value?.destroy();
});
async function init() {
const imgEl = unref(imgElRef);
if (!imgEl) {
return;
}
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true;
realTimeCroppered();
emit('ready', cropper.value);
},
crop() {
debounceRealTimeCroppered();
},
zoom() {
debounceRealTimeCroppered();
},
cropmove() {
debounceRealTimeCroppered();
},
...props.options,
});
}
// Real-time display preview const imgElRef = ref<ElRef<HTMLImageElement>>();
function realTimeCroppered() { const cropper = ref<Nullable<Cropper>>();
props.realTimePreview && croppered(); const isReady = ref(false);
}
// event: return base64 and width and height information after cropping const { prefixCls } = useDesign('cropper-image');
function croppered() { const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
if (!cropper.value) {
return;
}
let imgInfo = cropper.value.getData();
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
canvas.toBlob((blob) => {
if (!blob) {
return;
}
let fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onloadend = (e) => {
emit('cropend', {
imgBase64: e.target?.result ?? '',
imgInfo,
});
};
fileReader.onerror = () => {
emit('cropendError');
};
}, 'image/png');
}
// Get a circular picture canvas const getImageStyle = computed((): CSSProperties => {
function getRoundedCanvas() { return {
const sourceCanvas = cropper.value!.getCroppedCanvas(); height: props.height,
const canvas = document.createElement('canvas'); maxWidth: '100%',
const context = canvas.getContext('2d')!; ...props.imageStyle,
const width = sourceCanvas.width; };
const height = sourceCanvas.height; });
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
context.fill();
return canvas;
}
return { getClass, imgElRef, getWrapperStyle, getImageStyle, isReady, croppered }; const getClass = computed(() => {
}, return [
prefixCls,
attrs.class,
{
[`${prefixCls}--circled`]: props.circled,
},
];
});
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, '') + 'px' };
});
onMounted(init);
onUnmounted(() => {
cropper.value?.destroy();
}); });
async function init() {
const imgEl = unref(imgElRef);
if (!imgEl) {
return;
}
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true;
realTimeCroppered();
emits('ready', cropper.value);
},
crop() {
debounceRealTimeCroppered();
},
zoom() {
debounceRealTimeCroppered();
},
cropmove() {
debounceRealTimeCroppered();
},
...props.options,
});
}
// Real-time display preview
function realTimeCroppered() {
props.realTimePreview && croppered();
}
// event: return base64 and width and height information after cropping
function croppered() {
if (!cropper.value) {
return;
}
let imgInfo = cropper.value.getData();
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
canvas.toBlob((blob) => {
if (!blob) {
return;
}
let fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onloadend = (e) => {
emits('cropend', {
imgBase64: e.target?.result ?? '',
imgInfo,
});
};
fileReader.onerror = () => {
emits('cropendError');
};
}, 'image/png');
}
// Get a circular picture canvas
function getRoundedCanvas() {
const sourceCanvas = cropper.value!.getCroppedCanvas();
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
const width = sourceCanvas.width;
const height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
context.fill();
return canvas;
}
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image'; @prefix-cls: ~'@{namespace}-cropper-image';

112
apps/vue/src/components/Cropper/src/CropperAvatar.vue

@ -11,14 +11,14 @@
</div> </div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" /> <img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div> </div>
<a-button <Button
:class="`${prefixCls}-upload-btn`" :class="`${prefixCls}-upload-btn`"
@click="openModal" @click="openModal"
v-if="showBtn" v-if="showBtn"
v-bind="btnProps" v-bind="btnProps"
> >
{{ btnText ? btnText : t('component.cropper.selectImage') }} {{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button> </Button>
<CopperModal <CopperModal
@register="register" @register="register"
@ -28,9 +28,8 @@
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { import {
defineComponent,
computed, computed,
CSSProperties, CSSProperties,
unref, unref,
@ -39,6 +38,7 @@
watch, watch,
PropType, PropType,
} from 'vue'; } from 'vue';
import { Button } from '/@/components/Button';
import CopperModal from './CopperModal.vue'; import CopperModal from './CopperModal.vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
@ -47,72 +47,58 @@
import type { ButtonProps } from '/@/components/Button'; import type { ButtonProps } from '/@/components/Button';
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
const props = { interface File {
file: Blob;
name: string;
fileName?: string;
}
const emits = defineEmits(['update:value', 'change']);
const props = defineProps({
width: { type: [String, Number], default: '200px' }, width: { type: [String, Number], default: '200px' },
value: { type: String }, value: { type: String },
showBtn: { type: Boolean, default: true }, showBtn: { type: Boolean, default: true },
btnProps: { type: Object as PropType<ButtonProps> }, btnProps: { type: Object as PropType<ButtonProps> },
btnText: { type: String, default: '' }, btnText: { type: String, default: '' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> }, uploadApi: { type: Function as PropType<(file: File) => Promise<void>> },
}; });
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal, Icon },
props,
emits: ['update:value', 'change'],
setup(props, { emit, expose }) {
const sourceValue = ref(props.value || '');
const { prefixCls } = useDesign('cropper-avatar');
const [register, { openModal, closeModal }] = useModal();
const { createMessage } = useMessage();
const { t } = useI18n();
const getClass = computed(() => [prefixCls]);
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
);
watchEffect(() => {
sourceValue.value = props.value || '';
});
watch(
() => sourceValue.value,
(v: string) => {
emit('update:value', v);
},
);
function handleUploadSuccess({ source }) {
sourceValue.value = source;
emit('change', source);
createMessage.success(t('component.cropper.uploadSuccess'));
}
expose({ openModal: openModal.bind(null, true), closeModal }); const sourceValue = ref(props.value || '');
const { prefixCls } = useDesign('cropper-avatar');
return { const [register, { openModal, closeModal }] = useModal();
t, const { createMessage } = useMessage();
prefixCls, const { t } = useI18n();
register,
openModal: openModal as any, const getClass = computed(() => [prefixCls]);
getIconWidth,
sourceValue, const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
getClass,
getImageWrapperStyle, const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
getStyle,
handleUploadSuccess, const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
};
}, const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
);
watchEffect(() => {
sourceValue.value = props.value || '';
}); });
watch(
() => sourceValue.value,
(v: string) => {
emits('update:value', v);
},
);
function handleUploadSuccess({ source }) {
sourceValue.value = source;
emits('change', source);
createMessage.success(t('component.cropper.uploadSuccess'));
}
defineExpose({ openModal: openModal.bind(null, true), closeModal });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

146
apps/vue/src/components/registerGlobComp.ts

@ -3,17 +3,159 @@ import { Button } from './Button';
import { Input } from './Input'; import { Input } from './Input';
import { import {
// Need // Need
Affix,
Anchor,
AutoComplete,
Alert,
Avatar,
BackTop,
Badge,
Button as AntButton, Button as AntButton,
Breadcrumb,
Calendar,
Card,
Collapse,
Carousel,
Cascader,
Checkbox,
Col,
Comment,
DatePicker,
Descriptions,
Divider,
Dropdown,
Drawer,
Empty,
Form,
Image,
Layout, Layout,
List,
Mentions,
Statistic,
Pagination,
Popover,
Progress,
Radio,
Rate,
Result,
Row,
Select,
Skeleton,
Slider,
Space,
Spin,
Steps,
Switch,
Transfer,
Tabs,
Tag,
Timeline,
Tooltip,
Typography,
Upload,
Input as AInput, Input as AInput,
InputNumber as AInputNumber,
} from 'ant-design-vue'; } from 'ant-design-vue';
const compList = [AntButton.Group]; const compList = [
Affix,
Anchor,
Anchor.Link,
AntButton.Group,
AutoComplete,
Alert,
Avatar,
Avatar.Group,
BackTop,
Badge,
Badge.Ribbon,
Breadcrumb,
Breadcrumb.Item,
Breadcrumb.Separator,
Calendar,
Card,
Card.Grid,
Card.Meta,
Collapse,
Collapse.Panel,
Carousel,
Cascader,
Checkbox,
Checkbox.Group,
Col,
Comment,
DatePicker,
DatePicker.MonthPicker,
DatePicker.QuarterPicker,
DatePicker.RangePicker,
DatePicker.TimePicker,
DatePicker.WeekPicker,
DatePicker.YearPicker,
Descriptions,
Descriptions.Item,
Divider,
Dropdown,
Dropdown.Button,
Drawer,
Empty,
Form,
Form.Item,
Form.ItemRest,
Image,
AInput,
AInput.Group,
AInput.Password,
AInput.Search,
AInput.TextArea,
AInputNumber,
List,
Mentions,
Statistic,
Statistic.Countdown,
Pagination,
Popover,
Progress,
Radio,
Radio.Button,
Radio.Group,
Rate,
Result,
Row,
Select,
Select.OptGroup,
Select.Option,
Skeleton,
Skeleton.Button,
Skeleton.Avatar,
Skeleton.Image,
Skeleton.Input,
Slider,
Space,
Spin,
Steps,
Steps.Step,
Switch,
Transfer,
Tabs,
Tabs.TabPane,
Tag,
Tag.CheckableTag,
Timeline,
Timeline.Item,
Tooltip,
Typography,
Typography.Link,
Typography.Paragraph,
Typography.Text,
Typography.Title,
Upload,
Upload.Dragger,
];
export function registerGlobComp(app: App) { export function registerGlobComp(app: App) {
compList.forEach((comp) => { compList.forEach((comp) => {
app.component(comp.name || comp.displayName, comp); app.component(comp.name || comp.displayName, comp);
}); });
app.use(Input).use(AInput).use(Button).use(Layout); app.use(Input).use(Button).use(Layout);
} }

3
apps/vue/src/main.ts

@ -5,7 +5,6 @@ import 'virtual:windi-utilities.css';
// Register icon sprite // Register icon sprite
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register';
import App from './App.vue'; import App from './App.vue';
import Antd from 'ant-design-vue';
import VueCookies from 'vue-cookies'; import VueCookies from 'vue-cookies';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { initAppConfigStore, initAbpConfigStore } from '/@/logics/initAppConfig'; import { initAppConfigStore, initAbpConfigStore } from '/@/logics/initAppConfig';
@ -53,8 +52,6 @@ async function bootstrap() {
app.use(VueCookies); app.use(VueCookies);
app.use(Antd);
app.mount('#app'); app.mount('#app');
} }

Loading…
Cancel
Save