dc1a7e5e by lihua

门户首页

1 parent 7731293f
Showing 45 changed files with 5421 additions and 195 deletions
...@@ -101,6 +101,12 @@ VITE_APP_SERVICE_BASEURL = ms-daop-trust-api-service ...@@ -101,6 +101,12 @@ VITE_APP_SERVICE_BASEURL = ms-daop-trust-api-service
101 #数字合约接口,身份认证有关的服务 101 #数字合约接口,身份认证有关的服务
102 VITE_APP_DIGITAL_CONTRACT_URL = ms-daop-trust-data-space-service 102 VITE_APP_DIGITAL_CONTRACT_URL = ms-daop-trust-data-space-service
103 103
104 # 是否启用登录验证
105 VITE_verify = true
106
107 # 应用标识
108 VITE_appKey = '691689d8e4b0f359c04d204a'
109
104 # 本地访问地址 110 # 本地访问地址
105 # VITE_API_CIRCULATION_URL = http://localhost:9000/circulation 111 # VITE_API_CIRCULATION_URL = http://localhost:9000/circulation
106 112
......
...@@ -133,6 +133,9 @@ VITE_APP_CIRCULATION = http://192.168.6.20:18052/ ...@@ -133,6 +133,9 @@ VITE_APP_CIRCULATION = http://192.168.6.20:18052/
133 #数据加工交付 133 #数据加工交付
134 VITE_APP_DATA_DELIVERY = http://192.168.6.20:38052/ 134 VITE_APP_DATA_DELIVERY = http://192.168.6.20:38052/
135 135
136 # 是否启用登录验证
137 VITE_verify = true
138
136 # 是否在打包时生成 sourcemap 139 # 是否在打包时生成 sourcemap
137 VITE_BUILD_SOURCEMAP = false 140 VITE_BUILD_SOURCEMAP = false
138 # 是否在打包时开启压缩,支持 gzip 和 brotli 141 # 是否在打包时开启压缩,支持 gzip 和 brotli
......
...@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' { ...@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
21 Dialog_form: typeof import('./src/components/Dialog/dialog_form.vue')['default'] 21 Dialog_form: typeof import('./src/components/Dialog/dialog_form.vue')['default']
22 Dialog_grid: typeof import('./src/components/Dialog/dialog_grid.vue')['default'] 22 Dialog_grid: typeof import('./src/components/Dialog/dialog_grid.vue')['default']
23 Dialog_pane: typeof import('./src/components/Dialog/dialog_pane.vue')['default'] 23 Dialog_pane: typeof import('./src/components/Dialog/dialog_pane.vue')['default']
24 DialogPlus: typeof import('./src/components/DialogPlus/src/DialogPlus.vue')['default']
24 Drawer: typeof import('./src/components/Drawer/index.vue')['default'] 25 Drawer: typeof import('./src/components/Drawer/index.vue')['default']
25 EchartsMap: typeof import('./src/components/EchartsMap/index.vue')['default'] 26 EchartsMap: typeof import('./src/components/EchartsMap/index.vue')['default']
26 Editor: typeof import('./src/components/Editor/src/Editor.vue')['default'] 27 Editor: typeof import('./src/components/Editor/src/Editor.vue')['default']
...@@ -28,13 +29,17 @@ declare module '@vue/runtime-core' { ...@@ -28,13 +29,17 @@ declare module '@vue/runtime-core' {
28 FileUpload: typeof import('./src/components/FileUpload/index.vue')['default'] 29 FileUpload: typeof import('./src/components/FileUpload/index.vue')['default']
29 FixedActionBar: typeof import('./src/components/FixedActionBar/index.vue')['default'] 30 FixedActionBar: typeof import('./src/components/FixedActionBar/index.vue')['default']
30 Form: typeof import('./src/components/Form/index.vue')['default'] 31 Form: typeof import('./src/components/Form/index.vue')['default']
32 FormItem: typeof import('./src/components/FormItem/FormItem.vue')['default']
33 FormPlus: typeof import('./src/components/FormPlus/src/FormPlus.vue')['default']
31 GraphTopbar: typeof import('./src/components/RelationNetwork/graphTopbar.vue')['default'] 34 GraphTopbar: typeof import('./src/components/RelationNetwork/graphTopbar.vue')['default']
35 Header: typeof import('./src/components/Header/index.vue')['default']
32 Hour: typeof import('./src/components/Schedule/component/hour.vue')['default'] 36 Hour: typeof import('./src/components/Schedule/component/hour.vue')['default']
33 ImagePreview: typeof import('./src/components/ImagePreview/index.vue')['default'] 37 ImagePreview: typeof import('./src/components/ImagePreview/index.vue')['default']
34 ImagesUpload: typeof import('./src/components/ImagesUpload/index.vue')['default'] 38 ImagesUpload: typeof import('./src/components/ImagesUpload/index.vue')['default']
35 ImageUpload: typeof import('./src/components/ImageUpload/index.vue')['default'] 39 ImageUpload: typeof import('./src/components/ImageUpload/index.vue')['default']
36 LineageGraph: typeof import('./src/components/LineageGraph/index.vue')['default'] 40 LineageGraph: typeof import('./src/components/LineageGraph/index.vue')['default']
37 ListPanel: typeof import('./src/components/ListPanel/index.vue')['default'] 41 ListPanel: typeof import('./src/components/ListPanel/index.vue')['default']
42 Logo: typeof import('./src/components/Logo/index.vue')['default']
38 LookBpmn: typeof import('./src/components/ApprovalProcess/src/components/LookBpmn.vue')['default'] 43 LookBpmn: typeof import('./src/components/ApprovalProcess/src/components/LookBpmn.vue')['default']
39 Month: typeof import('./src/components/Schedule/component/month.vue')['default'] 44 Month: typeof import('./src/components/Schedule/component/month.vue')['default']
40 NotAllowed: typeof import('./src/components/NotAllowed/index.vue')['default'] 45 NotAllowed: typeof import('./src/components/NotAllowed/index.vue')['default']
...@@ -46,6 +51,7 @@ declare module '@vue/runtime-core' { ...@@ -46,6 +51,7 @@ declare module '@vue/runtime-core' {
46 PcasCascader: typeof import('./src/components/PcasCascader/index.vue')['default'] 51 PcasCascader: typeof import('./src/components/PcasCascader/index.vue')['default']
47 Popover: typeof import('./src/components/Popover/index.vue')['default'] 52 Popover: typeof import('./src/components/Popover/index.vue')['default']
48 RelationNetwork: typeof import('./src/components/RelationNetwork/index.vue')['default'] 53 RelationNetwork: typeof import('./src/components/RelationNetwork/index.vue')['default']
54 Retrievepassword: typeof import('./src/components/Retrievepassword/index.vue')['default']
49 RouterLink: typeof import('vue-router')['RouterLink'] 55 RouterLink: typeof import('vue-router')['RouterLink']
50 RouterView: typeof import('vue-router')['RouterView'] 56 RouterView: typeof import('vue-router')['RouterView']
51 Schedule: typeof import('./src/components/Schedule/index.vue')['default'] 57 Schedule: typeof import('./src/components/Schedule/index.vue')['default']
......
1 {
2 "appKey": "67bd3018e4b0cac8f9a5beeb"
3 }
...@@ -216,4 +216,4 @@ export const addorUpdateStaff = (param) => request({ ...@@ -216,4 +216,4 @@ export const addorUpdateStaff = (param) => request({
216 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/staff/save-or-update`, 216 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/staff/save-or-update`,
217 method: 'put', 217 method: 'put',
218 data: param 218 data: param
219 }); 219 });
...\ No newline at end of file ...\ No newline at end of file
......
1 import request from "@/utils/request";
2
3 export const idaasLogin = (params) => request({
4 url: `/oauth/login`,
5 method: 'idaasPost',
6 headers: {
7 'Content-Type': 'application/x-www-form-urlencoded'
8 },
9 data: params,
10 })
11
12 export const getLoginWebAuthn = () => request({
13 url: `/webauthn/assertion/options`,
14 method: 'idaasGet',
15 })
16
17 export const getPictureCode = () => request({
18 url: `/user/get-picture-code`,
19 method: 'get',
20 })
21
22 // 通用图形验证码
23 export const commoncheckImgCode = () => request({
24 url: `/ms-daop-user-service/user/validate-code/get`,
25 method: 'get',
26 responseType: 'blob'
27 })
28
29 // 校验图形验证码
30 export const checkImgCode = (params) => request({
31 url: `${import.meta.env.VITE_APP_AUTH_URL}/portal/check`,
32 method: 'post',
33 data: params
34 });
35
36 // 获取图形验证码
37 export const getImgCodeSrc = (params) => request({
38 url: `${import.meta.env.VITE_APP_AUTH_URL}/portal/get-captcha`,
39 method: 'get',
40 params
41 });
42
43 export const checkDeviceTypeRegist = (params) => request({
44 url: `/web-authn/check-device-type-regist?logonUser=${params.logonUser}&deviceType=${params.platform}`,
45 method: 'idaasGet'
46 });
47
48 /**
49 * 校验登录用户账号和密码
50 * @param logonUser 用户手机号
51 * @returns
52 */
53 export const checkLoginUser = (logonUser:string) => request({
54 url: `/ms-daop-user-service/user/check-login-user-password?logonUser=${logonUser}`,
55 method: 'get'
56 });
57
58 export const registWebAuthn = () => request({
59 url: `/webauthn/attestation/options`,
60 method: 'idaasGet'
61 });
62
63 export const signUp = (data) => request({
64 url: `/web-authn/signup`,
65 method: 'idaasPost',
66 data: data
67 });
68
69 /** 发送登录验证码到手机 */
70 export const sendLoginCode = (mobileNo:string) => request({
71 url: `/ms-daop-user-service/user/get-login-sms-validate-code?mobileNo=${mobileNo}`,
72 method: 'get'
73 });
74
75 export const getWebAuth4jLogin = (data) => request({
76 url: `/webauthn/login`,
77 method: 'idaasPost',
78 data: data
79 });
...\ No newline at end of file ...\ No newline at end of file
1 import DialogPlus from './src/DialogPlus.vue'
2
3 export { DialogPlus }
1 <script setup lang="ts">
2 import { ElDialog, ElScrollbar } from 'element-plus'
3 import { propTypes } from '@/utils/propTypes'
4 import { computed, useAttrs, ref, unref, useSlots, watch, nextTick } from 'vue'
5 const slots = useSlots()
6
7 const props = defineProps({
8 modelValue: propTypes.bool.def(false),
9 scrollbar: propTypes.bool.def(true),
10 title: propTypes.string.def('Dialog'),
11 fullscreen: propTypes.bool.def(true),
12 maxHeight: propTypes.oneOfType([String, Number]).def('400px'),
13 minHeight: propTypes.oneOfType([String, Number]).def('140px')
14 })
15
16 const getBindValue = computed(() => {
17 const delArr: string[] = ['fullscreen', 'title', 'maxHeight']
18 const attrs = useAttrs()
19 const obj = { ...attrs, ...props }
20 for (const key in obj) {
21 if (delArr.indexOf(key) !== -1) {
22 delete obj[key]
23 }
24 }
25 return obj
26 })
27
28 const isFullscreen = ref(false)
29
30 const isNumber = (val) => {
31 return val && typeof props.maxHeight == 'number'
32
33 }
34
35 const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
36
37 watch(
38 () => isFullscreen.value,
39 async (val: boolean) => {
40 await nextTick()
41 if (val) {
42 const windowHeight = document.documentElement.offsetHeight
43 dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
44 } else {
45 dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
46 }
47 },
48 {
49 immediate: true
50 }
51 )
52
53 const dialogStyle = computed(() => {
54 return {
55 height: unref(dialogHeight),
56 'max-height': isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight,
57 'min-height': isNumber(props.minHeight) ? `${props.minHeight}px` : props.minHeight,
58 }
59 })
60 </script>
61
62 <template>
63 <ElDialog v-bind="getBindValue" :fullscreen="isFullscreen" destroy-on-close lock-scroll draggable top="15vh"
64 :close-on-click-modal="false" :show-close="true">
65 <template #header="{ close }">
66 <div class="flex justify-between items-center h-54px relative">
67 <slot name="title">
68 {{ title }}
69 </slot>
70 <!-- <div class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]">
71 <Icon v-if="fullscreen" class="cursor-pointer is-hover !h-54px mr-10px"
72 :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
73 color="var(--el-color-info)" hover-color="var(--el-color-primary)" @click="toggleFull" />
74 <Icon class="cursor-pointer is-hover !h-54px" icon="ep:close" hover-color="var(--el-color-primary)"
75 color="var(--el-color-info)" @click="close" />
76 </div> -->
77 </div>
78 </template>
79
80 <ElScrollbar v-if="scrollbar" :style="dialogStyle">
81 <slot></slot>
82 </ElScrollbar>
83 <div v-else :style="dialogStyle">
84 <slot></slot>
85 </div>
86
87 <template v-if="slots.footer" #footer>
88 <slot name="footer"></slot>
89 </template>
90 </ElDialog>
91 </template>
92
93 <style lang="scss" scoped>
94 .el-overlay-dialog {
95 display: flex;
96 justify-content: center;
97 align-items: center;
98
99 :deep .el-dialog {
100 margin: 0 !important;
101
102 :deep .el-dialog__header {
103 padding: 0 20px;
104 height: 50px;
105 border-bottom: 1px solid #d9d9d9;
106 margin-right: 0;
107 display: block !important;
108 align-items: center;
109
110 .el-dialog__headerbtn {
111 width: 50px;
112 height: 50px;
113 top: 0;
114 }
115 }
116
117 &__header {
118 height: 54px;
119 padding: 0;
120 margin-right: 0 !important;
121 border-bottom: 1px solid var(--el-border-color);
122 }
123
124 &__body {
125 padding: 15px !important;
126 }
127
128 &__footer {
129 border-top: 1px solid var(--el-border-color);
130 }
131
132 &__headerbtn {
133 top: 0;
134 }
135 }
136 }
137 </style>
1 <script setup lang="ts">
2 import { FormPlus, FormSchema } from '@/components/FormPlus'
3 import { useForm } from '@/hooks/useForm'
4 import { propTypes } from "@/utils/propTypes";
5 import { reactive, unref, ref, computed } from 'vue'
6 import { ElButton, ElInput, FormItemProp } from 'element-plus'
7 import { useValidator } from '@/hooks/useValidator'
8 import { ComponentRef } from '@/types/global';
9
10 const props = defineProps({
11 schemaParam: {
12 type: Object as PropType<FormSchema[]>,
13 default: () => {
14 return []
15 }
16 },
17 labelPosition: propTypes.string.def('top'),
18 disabled: propTypes.bool.def(false),
19 model: {
20 type: Object as PropType<any>,
21 default: () => {
22 return {}
23 }
24 }
25 })
26
27 const formModel = computed(() => {
28 console.log(unref(props.model), 'model');
29 return props.model
30 })
31 const schemaData = computed(() => reactive<FormSchema[]>(unref(props.schemaParam)))
32
33 const { scrollToError } = useValidator()
34
35
36 const { formRegister, formMethods } = useForm()
37 const {
38 getFormResult,
39 setProps,
40 setValues,
41 setSchema,
42 getComponentExpose,
43 getFormItemExpose,
44 getElFormExpose,
45 getFormData
46 } = formMethods
47
48 /**
49 * 表单根据字段校验
50 * @param fields 待验证的字段
51 */
52 const formValidation = async (fields: string[] = []) => { // 表单验证
53 const elFormExpose = await getElFormExpose()
54 return new Promise(async (resolve) => {
55 elFormExpose?.validateField(fields, (isValid) => {
56 if (isValid) {
57 resolve(isValid)
58 } else {
59 resolve(isValid)
60 }
61 })
62 })
63 }
64
65 const clearValidate = async (fields: string[] = []) => { // 表单验证
66 const elFormExpose = await getElFormExpose()
67 elFormExpose?.clearValidate(fields)
68 }
69
70 const inoutFocus = async () => {
71 const inputEl: ComponentRef<typeof ElInput> = await getComponentExpose('field1')
72 inputEl?.focus()
73 }
74
75 const inoutValidation = async () => {
76 const formItem = await getFormItemExpose('field1')
77 const inputEl: ComponentRef<typeof ElInput> = await getComponentExpose('field1')
78 inputEl?.focus()
79 formItem?.validate('focus', (val: boolean) => {
80 console.log(val)
81 })
82 }
83
84 const formValidate = (prop: FormItemProp, isValid: boolean, message: string) => {
85 // console.log(prop, isValid, message)
86 }
87
88 /**
89 * @param filterEmptyVal 是否过滤空值
90 */
91 const getData = (filterEmptyVal = true) => {
92 if (filterEmptyVal) {
93 return new Promise(async (resolve) => {
94 const resultForm = await getFormData(filterEmptyVal)
95 resolve(resultForm)
96 })
97 } else {
98 return new Promise(async (resolve) => {
99 const resultForm = await getFormData(filterEmptyVal)
100 let res = getFormResult(resultForm, schemaData.value)
101 resolve(res)
102 })
103 }
104 }
105
106 const setValue = async (param = {}, reset = false) => {
107 const elFormExpose = await getElFormExpose()
108 if (reset) {
109 elFormExpose?.resetFields()
110 } else {
111 setValues(param)
112 }
113 }
114
115 const submitForm = () => {
116 return new Promise(async (resolve) => {
117 const elFormExpose = await getElFormExpose()
118 scrollToError()
119 elFormExpose?.validate((isValid) => {
120 if (isValid) {
121 resolve(isValid)
122 } else {
123 resolve(isValid)
124 }
125 })
126 })
127 }
128
129 // 子组建暴露的方法
130 defineExpose({
131 formValidation,
132 getFormResult,
133 clearValidate,
134 setSchema,
135 submitForm,
136 getData,
137 setValue: setValue,
138 setValues: setValue,
139 formMethods
140 })
141 </script>
142
143 <template>
144 <div>
145 <FormPlus :schema="schemaData" :disabled="disabled" :model="formModel" :isCol="true" :labelPosition="labelPosition"
146 @register="formRegister" @validate="formValidate" />
147 </div>
148 </template>
149
150 <style lang="scss" scoped>
151 .el-button {
152 margin-top: 10px;
153 }
154 </style>
1 import FormItem from './FormItem.vue';
2 export default FormItem
1 import FormPlus from './src/FormPlus.vue'
2 import type { FormSchema, FormSetProps } from './src/types'
3 export type {
4 ComponentNameEnum,
5 ComponentName,
6 InputComponentProps,
7 AutocompleteComponentProps,
8 InputNumberComponentProps,
9 SelectOption,
10 SelectComponentProps,
11 SelectV2ComponentProps,
12 CascaderComponentProps,
13 SwitchComponentProps,
14 RateComponentProps,
15 ColorPickerComponentProps,
16 TransferComponentProps,
17 RadioOption,
18 RadioGroupComponentProps,
19 RadioButtonComponentProps,
20 CheckboxOption,
21 CheckboxGroupComponentProps,
22 DividerComponentProps,
23 DatePickerComponentProps,
24 DateTimePickerComponentProps,
25 TimePickerComponentProps,
26 TimeSelectComponentProps,
27 ColProps,
28 FormSetProps,
29 FormItemProps,
30 FormSchema,
31 FormProps,
32 PlaceholderModel,
33 InputPasswordComponentProps,
34 TreeSelectComponentProps
35 } from './src/types'
36
37 export interface FormExpose {
38 setValues: (data: any) => void
39 setProps: (props: any) => void
40 delSchema: (field: string,deleteField:boolean) => void
41 addSchema: (formSchema: FormSchema, index?: number) => void
42 setSchema: (schemaProps: FormSetProps[]) => void
43 formModel: any
44 getComponentExpose: (field: string) => any
45 getFormItemExpose: (field: string) => any
46 }
47
48 export { FormPlus }
1 <script lang="tsx">
2 import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
3 import {
4 ElForm,
5 ElFormItem,
6 ElRow,
7 ElCol,
8 FormRules,
9 ComponentSize
10 // FormItemProp
11 } from 'element-plus'
12 import { componentMap } from './helper/componentMap'
13 import { propTypes } from '@/utils/propTypes'
14 import {
15 setTextPlaceholder,
16 setGridProp,
17 setComponentProps,
18 setItemComponentSlots,
19 initModel
20 } from './helper'
21 import { useRenderSelect } from './components/useRenderSelect'
22 import { useRenderRadio } from './components/useRenderRadio'
23 import { useRenderCheckbox } from './components/useRenderCheckbox'
24 import { get, set } from 'lodash-es'
25 import { FormProps } from './types'
26 import {
27 FormSchema,
28 FormSetProps,
29 ComponentNameEnum,
30 SelectComponentProps,
31 RadioGroupComponentProps,
32 CheckboxGroupComponentProps
33 } from './types'
34 import { ComponentRef, Recordable } from '@/types/global'
35 import { Slots } from 'vue'
36
37 const { renderSelectOptions } = useRenderSelect()
38 const { renderRadioOptions } = useRenderRadio()
39 const { renderCheckboxOptions } = useRenderCheckbox()
40
41 export const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => {
42 // Reflect.has 判断一个对象是否存在某个属性
43 if (!slots || !Reflect.has(slots, slot)) {
44 return null
45 }
46 if (typeof slots[slot] !== 'function') {
47 console.error(`${slot} is not a function!`)
48 return null
49 }
50 const slotFn = slots[slot]
51 if (!slotFn) return null
52 return slotFn(data)
53 }
54
55
56 export default defineComponent({
57 name: 'Form',
58 props: {
59 // 生成Form的布局结构数组
60 schema: {
61 type: Array as PropType<FormSchema[]>,
62 default: () => []
63 },
64 // 是否需要栅格布局
65 isCol: propTypes.bool.def(true),
66 // 表单数据对象
67 model: {
68 type: Object as PropType<any>,
69 default: () => ({})
70 },
71 // 是否自动设置placeholder
72 autoSetPlaceholder: propTypes.bool.def(true),
73 // 是否自定义内容
74 isCustom: propTypes.bool.def(false),
75 // 表单label宽度
76 labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
77 rules: {
78 type: Object as PropType<FormRules>,
79 default: () => ({})
80 },
81 labelPosition: propTypes.oneOf(['left', 'right', 'top']).def('right'),
82 labelSuffix: propTypes.string.def(''),
83 hideRequiredAsterisk: propTypes.bool.def(false),
84 requireAsteriskPosition: propTypes.oneOf(['left', 'right']).def('right'),
85 showMessage: propTypes.bool.def(true),
86 inlineMessage: propTypes.bool.def(false),
87 statusIcon: propTypes.bool.def(false),
88 validateOnRuleChange: propTypes.bool.def(true),
89 size: {
90 type: String as PropType<ComponentSize>,
91 default: undefined
92 },
93 disabled: propTypes.bool.def(false),
94 scrollToError: propTypes.bool.def(false),
95 scrollToErrorOffset: propTypes.oneOfType([Boolean, Object]).def(undefined)
96 // onValidate: {
97 // type: Function as PropType<(prop: FormItemProp, isValid: boolean, message: string) => void>,
98 // default: () => {}
99 // }
100 },
101 emits: ['register'],
102 setup(props, { slots, expose, emit }) {
103 // element form 实例
104 const elFormRef = ref<ComponentRef<typeof ElForm>>()
105
106 const mergeProps = ref<FormProps>({})
107
108 const getProps = computed(() => {
109 const propsObj = { ...props }
110 Object.assign(propsObj, unref(mergeProps))
111 return propsObj
112 })
113
114 // 存储表单实例
115 const formComponents = ref({})
116
117 // 存储form-item实例
118 const formItemComponents = ref({})
119
120 // 表单数据
121 const formModel = ref<any>(props.model)
122
123 onMounted(() => {
124 emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
125 })
126
127 // 对表单赋值
128 const setValues = (data = {}) => {
129 formModel.value = Object.assign(unref(formModel), data)
130 }
131
132 const setProps = (props: FormProps = {}) => {
133 mergeProps.value = Object.assign(unref(mergeProps), props)
134 }
135
136 const delSchema = (field: string,deleteField:boolean) => {
137 const { schema } = unref(getProps)
138
139 const index = schema.findIndex(v => v.field === field);
140 if (index > -1) {
141 // #删除对应的字段
142 if (formModel.value[field] && deleteField) delete formModel.value[field]
143 schema.splice(index, 1)
144 }
145 }
146
147 const addSchema = (formSchema: FormSchema, index?: number) => {
148 const { schema } = unref(getProps)
149 if (index !== void 0) {
150 schema.splice(index, 0, formSchema)
151 return
152 }
153 schema.push(formSchema)
154 }
155
156 const addSchemas = (formSchemas: FormSchema[], index?: number) => {
157 const { schema } = unref(getProps)
158 if (index !== void 0) {
159 schema.splice(index, 0, ...formSchemas)
160 } else {
161 schema.push(...formSchemas)
162 }
163 }
164
165 const setSchema = (schemaProps: FormSetProps[]) => {
166 const { schema } = unref(getProps)
167 for (const v of schema) {
168 for (const item of schemaProps) {
169 if (v.field === item.field) {
170 set(v, item.path, item.value)
171 }
172 }
173 }
174 }
175
176 const getOptions = async (fn: Function, item: FormSchema) => {
177 const options = await fn()
178 setSchema([
179 {
180 field: item.field,
181 path:
182 item.component === ComponentNameEnum.TREE_SELECT
183 ? 'componentProps.data'
184 : 'componentProps.options',
185 value: options
186 }
187 ])
188 }
189
190 /**
191 * @description: 获取表单组件实例
192 * @param filed 表单字段
193 */
194 const getComponentExpose = (filed: string) => {
195 return unref(formComponents)[filed]
196 }
197
198 /**
199 * @description: 获取formItem实例
200 * @param filed 表单字段
201 */
202 const getFormItemExpose = (filed: string) => {
203 return unref(formItemComponents)[filed]
204 }
205
206 const setComponentRefMap = (ref: any, filed: string) => {
207 formComponents.value[filed] = ref
208 }
209
210 const setFormItemRefMap = (ref: any, filed: string) => {
211 formItemComponents.value[filed] = ref
212 }
213
214 expose({
215 setValues,
216 formModel,
217 setProps,
218 delSchema,
219 addSchema,
220 addSchemas,
221 setSchema,
222 getComponentExpose,
223 getFormItemExpose
224 })
225
226 // 监听表单结构化数组,重新生成formModel
227 watch(
228 () => unref(getProps).schema,
229 (schema = []) => {
230 formModel.value = initModel(schema, unref(formModel))
231 },
232 {
233 immediate: true,
234 deep: true
235 }
236 )
237
238 // 渲染包裹标签,是否使用栅格布局
239 const renderWrap = () => {
240 const { isCol } = unref(getProps)
241 const content = isCol ? (
242 <ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
243 ) : (
244 renderFormItemWrap()
245 )
246 return content
247 }
248
249 // 是否要渲染el-col
250 const renderFormItemWrap = () => {
251 // hidden属性表示隐藏,不做渲染
252 const { schema = [], isCol } = unref(getProps)
253 // schema.forEach(item=>{ // 默认显示
254 // item.visible = true
255 // })
256 return schema
257 .filter((v) => !v.remove && (v.visible ?? true))
258 .map((item) => {
259 // 如果是 Divider 组件,需要自己占用一行
260 const isDivider = item.component === 'Divider'
261 const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>
262 return isDivider ? (
263 <Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
264 ) : isCol ? (
265 // 如果需要栅格,需要包裹 ElCol
266 <ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
267 ) : (
268 renderFormItem(item)
269 )
270 })
271 }
272
273 // 渲染formItem
274 const renderFormItem = (item: FormSchema) => {
275 // 如果有optionApi,优先使用optionApi
276 if (item.optionApi) {
277 // 内部自动调用接口,不影响其它渲染
278 getOptions(item.optionApi, item)
279 }
280 const formItemSlots: Recordable = {
281 default: () => {
282 if (item?.formItemProps?.slots?.default) {
283 return item?.formItemProps?.slots?.default(formModel.value)
284 } else {
285 const Com = componentMap[item.component as string] as ReturnType<typeof defineComponent>
286
287 const { autoSetPlaceholder } = unref(getProps)
288
289 const componentSlots = (item?.componentProps as any)?.slots || {}
290 const slotsMap: Recordable = {
291 ...setItemComponentSlots(componentSlots)
292 }
293 // 如果是select组件,并且没有自定义模板,自动渲染options
294 if (item.component === ComponentNameEnum.SELECT) {
295 slotsMap.default = !componentSlots.default
296 ? () => renderSelectOptions(item)
297 : () => {
298 return componentSlots.default(
299 unref((item?.componentProps as SelectComponentProps)?.options)
300 )
301 }
302 }
303
304 // 虚拟列表
305 if (item.component === ComponentNameEnum.SELECT_V2 && componentSlots.default) {
306 slotsMap.default = ({ item }) => {
307 return componentSlots.default(item)
308 }
309 }
310
311 // 单选框组和按钮样式
312 if (
313 item.component === ComponentNameEnum.RADIO_GROUP ||
314 item.component === ComponentNameEnum.RADIO_BUTTON
315 ) {
316 slotsMap.default = !componentSlots.default
317 ? () => renderRadioOptions(item,formModel)
318 : () => {
319 return componentSlots.default(
320 unref((item?.componentProps as CheckboxGroupComponentProps)?.options)
321 )
322 }
323 }
324
325 // 多选框组和按钮样式
326 if (
327 item.component === ComponentNameEnum.CHECKBOX_GROUP ||
328 item.component === ComponentNameEnum.CHECKBOX_BUTTON
329 ) {
330 slotsMap.default = !componentSlots.default
331 ? () => renderCheckboxOptions(item)
332 : () => {
333 return componentSlots.default(
334 unref((item?.componentProps as RadioGroupComponentProps)?.options)
335 )
336 }
337 }
338
339 const Comp = () => {
340 // 如果field是多层路径,需要转换成对象
341 const itemVal = computed({
342 get: () => {
343 return get(formModel.value, item.field)
344 },
345 set: (val) => {
346 set(formModel.value, item.field, val)
347 }
348 })
349
350 return (
351 <Com
352 vModel={itemVal.value}
353 // 数字框禁用e常数
354 onkeypress={(event)=>{
355 if (item.component === ComponentNameEnum.INPUT_NUMBER) {
356 return (/[\d . -]/.test(String.fromCharCode(event.keyCode)))
357 }
358 }}
359 ref={(el: any) => setComponentRefMap(el, item.field)}
360 {...(autoSetPlaceholder && setTextPlaceholder(item))}
361 {...setComponentProps(item,unref(getProps))}
362 style={
363 item.componentProps?.style || {
364 width: '100%'
365 }
366 }
367 >
368 {{ ...slotsMap }}
369 </Com>
370 )
371 }
372
373 return <>{Comp()}</>
374 }
375 }
376 }
377 if (item?.formItemProps?.slots?.label) {
378 formItemSlots.label = (...args: any[]) => {
379 return (item?.formItemProps?.slots as any)?.label(...args)
380 }
381 }
382 if (item?.formItemProps?.slots?.error) {
383 formItemSlots.error = (...args: any[]) => {
384 return (item?.formItemProps?.slots as any)?.error(...args)
385 }
386 }
387 return (
388 <ElFormItem
389 v-show={!item.hidden}
390 ref={(el: any) => setFormItemRefMap(el, item.field)}
391 {...(item.formItemProps || {})}
392 prop={item.field}
393 label={item.label || ''}
394 >
395 {formItemSlots}
396 </ElFormItem>
397 )
398 }
399
400 // 过滤传入Form组件的属性
401 const getFormBindValue = () => {
402 // 避免在标签上出现多余的属性
403 const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
404 const props = { ...unref(getProps) }
405 for (const key in props) {
406 if (delKeys.indexOf(key) !== -1) {
407 delete props[key]
408 }
409 }
410 return props as FormProps
411 }
412
413 return () => (
414 <ElForm
415 ref={elFormRef}
416 {...getFormBindValue()}
417 model={unref(getProps).isCustom ? unref(getProps).model : formModel}
418 >
419 {{
420 // 如果需要自定义,就什么都不渲染,而是提供默认插槽
421 default: () => {
422 const { isCustom } = unref(getProps)
423 return isCustom ? getSlot(slots, 'default') : renderWrap()
424 }
425 }}
426 </ElForm>
427 )
428 }
429 })
430 </script>
431
432 <style lang="scss" scoped>
433 // .@{elNamespace}-form.@{namespace}-form .@{elNamespace}-row {
434 // margin-right: 0 !important;
435 // margin-left: 0 !important;
436 // }
437
438 // .@{elNamespace}-form--inline .@{elNamespace}-input {
439 // width: 245px;
440 // }
441 :deep(.el-form-item) {
442 .el-form-item__label {
443 margin-bottom: 2px !important
444 }
445 .el-input-number .el-input__inner {
446 text-align: left;
447 }
448 .el-radio {
449 .el-radio__inner::after {
450 width: 10px;
451 height: 10px;
452 }
453
454 .el-radio__input.is-checked .el-radio__inner {
455 background-color: inherit;
456
457 &::after {
458 background-color: var(--el-color-primary);
459 }
460 }
461 }
462 }
463 </style>
1
2 interface RenderHeader {
3 label:string // 列label
4 labelClass?:string
5 style?:string
6 iconProps?:IconProps
7 popoverProps:PopoverProps
8 }
9 interface IconProps {
10 iconClass?:string // 图标类名
11 icon:string // 图标名称
12 }
13 interface PopoverProps {
14 visible?:boolean
15 popoverContent:string // 提示内容
16 popoverWidth?:number // 提示框宽度
17 popoverTrigger? : 'click' | 'focus' | 'hover' | 'contextmenu' // 触发方式
18 popoverTitle?:string // 提示标题
19 }
20
21 export const useRenderAmountInput = () => {
22 /**
23 * 金额input渲染函数
24 * @param param
25 * @param isDetail 是否详情
26 * @returns
27 */
28 const renderAmountInput = (param:RenderHeader,isDetail?:boolean) => {
29 return (
30 <>
31
32 </>
33 );
34 }
35
36 return {
37 renderAmountInput
38 }
39 }
...\ No newline at end of file ...\ No newline at end of file
1 import { FormSchema, ComponentNameEnum, CheckboxGroupComponentProps } from '../types'
2 import { ElCheckbox, ElCheckboxButton } from 'element-plus'
3 import { defineComponent } from 'vue'
4
5 export const useRenderCheckbox = () => {
6 const renderCheckboxOptions = (item: FormSchema) => {
7 // 如果有别名,就取别名
8 const componentProps = item?.componentProps as CheckboxGroupComponentProps
9 const valueAlias = componentProps?.props?.value || 'value'
10 const labelAlias = componentProps?.props?.label || 'label'
11 const disabledAlias = componentProps?.props?.disabled || 'disabled'
12 const Com = (
13 item.component === ComponentNameEnum.CHECKBOX_GROUP ? ElCheckbox : ElCheckboxButton
14 ) as ReturnType<typeof defineComponent>
15 return componentProps?.options?.map((option) => {
16 const { value, ...other } = option
17 return (
18 <Com
19 {...other}
20 disabled={option[disabledAlias || 'disabled']}
21 label={option[valueAlias || 'value']}
22 >
23 {option[labelAlias || 'label']}
24 </Com>
25 )
26 })
27 }
28
29 return {
30 renderCheckboxOptions
31 }
32 }
1 import { FormSchema, ComponentNameEnum, RadioGroupComponentProps } from '../types'
2 import { ElRadio, ElRadioButton, ElIcon } from 'element-plus'
3 import { defineComponent } from 'vue'
4 import { Check } from "@element-plus/icons-vue"
5 import '../styles/radio.scss'
6
7 export const useRenderRadio = () => {
8 const renderRadioOptions = (item: FormSchema, formModel: any) => {
9 // 如果有别名,就取别名
10 const { field } = item
11 const componentProps = item?.componentProps as RadioGroupComponentProps
12 const valueAlias = componentProps?.props?.value || 'value'
13 const labelAlias = componentProps?.props?.label || 'label'
14 const disabledAlias = componentProps?.props?.disabled || 'disabled'
15 const Com = (
16 item.component === ComponentNameEnum.RADIO_GROUP ? ElRadio : ElRadioButton
17 ) as ReturnType<typeof defineComponent>
18 return componentProps?.options?.map((option) => {
19 const { value, ...other } = option
20 return (
21 <div class={other.border ? 'radio_panel' : ''}>
22 <Com
23 {...other}
24 disabled={option[disabledAlias || 'disabled']}
25 label={option[valueAlias || 'value']}
26 style='margin-right:8px'
27 >
28 {option[labelAlias || 'label']}
29 {
30 other.border ?
31 (<ElIcon class={ formModel.value[field] == value ? 'active corner_mark':'corner_mark' }>
32 <Check></Check>
33 </ElIcon>) : undefined
34 }
35 </Com>
36 </div>
37 )
38 })
39 }
40
41 return {
42 renderRadioOptions
43 }
44 }
1 import { ElOption, ElOptionGroup } from 'element-plus'
2 import { FormSchema, SelectComponentProps, SelectOption } from '../types'
3
4 export const useRenderSelect = () => {
5 // 渲染 select options
6 const renderSelectOptions = (item: FormSchema) => {
7 const componentsProps = item?.componentProps as SelectComponentProps
8 const optionGroupDefaultSlot = componentsProps?.slots?.optionGroupDefault
9 // 如果有别名,就取别名
10 const labelAlias = componentsProps?.props?.label
11 const keyAlias = componentsProps?.props?.key
12 return componentsProps?.options?.map((option) => {
13 if (option?.options?.length) {
14 return optionGroupDefaultSlot ? (
15 optionGroupDefaultSlot(option)
16 ) : (
17 <ElOptionGroup label={option[labelAlias || 'label']} key={option[keyAlias || 'key']}>
18 {{
19 default: () =>
20 option?.options?.map((v) => {
21 return renderSelectOptionItem(item, v)
22 })
23 }}
24 </ElOptionGroup>
25 )
26 } else {
27 return renderSelectOptionItem(item, option)
28 }
29 })
30 }
31
32 // 渲染 select option item
33 const renderSelectOptionItem = (item: FormSchema, option: SelectOption) => {
34 // 如果有别名,就取别名
35 const componentsProps = item.componentProps as SelectComponentProps
36 const labelAlias = componentsProps?.props?.label
37 const valueAlias = componentsProps?.props?.value
38 const keyAlias = componentsProps?.props?.key
39 const optionDefaultSlot = componentsProps.slots?.optionDefault
40
41 return (
42 <ElOption
43 {...option}
44 key={option[keyAlias || 'key']}
45 label={option[labelAlias || 'label']}
46 value={option[valueAlias || 'value']}
47 disabled={option.disabled}
48 >
49 {{
50 default: () => (optionDefaultSlot ? optionDefaultSlot(option) : undefined)
51 }}
52 </ElOption>
53 )
54 }
55
56 return {
57 renderSelectOptions
58 }
59 }
1 import type { Component } from 'vue'
2 import {
3 ElCascader,
4 ElCheckboxGroup,
5 ElColorPicker,
6 ElDatePicker,
7 ElInput,
8 ElInputNumber,
9 ElRadioGroup,
10 ElRate,
11 ElSelect,
12 ElSelectV2,
13 ElSlider,
14 ElSwitch,
15 ElTimePicker,
16 ElTimeSelect,
17 ElTransfer,
18 ElAutocomplete,
19 ElDivider,
20 ElTreeSelect,
21 ElUpload
22 } from 'element-plus'
23 // import { InputPassword } from '@/components/InputPassword'
24 // import { Editor } from '@/components/Editor'
25 // import { JsonEditor } from '@/components/JsonEditor'
26 // import { IconPicker } from '@/components/IconPicker'
27 import { ComponentName } from '../types'
28 import { Recordable } from '@/types/global'
29
30 const componentMap: Recordable<Component, ComponentName> = {
31 RadioGroup: ElRadioGroup,
32 RadioButton: ElRadioGroup,
33 CheckboxGroup: ElCheckboxGroup,
34 CheckboxButton: ElCheckboxGroup,
35 Input: ElInput,
36 Autocomplete: ElAutocomplete,
37 InputNumber: ElInputNumber,
38 Select: ElSelect,
39 Cascader: ElCascader,
40 Switch: ElSwitch,
41 Slider: ElSlider,
42 TimePicker: ElTimePicker,
43 DatePicker: ElDatePicker,
44 Rate: ElRate,
45 ColorPicker: ElColorPicker,
46 Transfer: ElTransfer,
47 Divider: ElDivider,
48 TimeSelect: ElTimeSelect,
49 SelectV2: ElSelectV2,
50 TreeSelect: ElTreeSelect,
51 Upload: ElUpload,
52 // InputPassword: InputPassword,
53 // Editor: Editor,
54 // JsonEditor: JsonEditor,
55 // IconPicker: IconPicker
56 }
57
58 export { componentMap }
1
2 import { firstUpperCase, humpToDash } from '@/utils/common'
3 import { PlaceholderModel, FormSchema, ComponentNameEnum, ColProps } from '../types'
4 import { set, get } from 'lodash-es'
5 import { Recordable } from '@/types/global'
6
7 /**
8 *
9 * @param schema 对应组件数据
10 * @returns 返回提示信息对象
11 * @description 用于自动设置placeholder
12 */
13 export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
14 const textMap = [
15 ComponentNameEnum.INPUT,
16 ComponentNameEnum.AUTOCOMPLETE,
17 ComponentNameEnum.INPUT_NUMBER,
18 ComponentNameEnum.INPUT_PASSWORD
19 ]
20 const selectMap = [
21 ComponentNameEnum.SELECT,
22 ComponentNameEnum.TIME_PICKER,
23 ComponentNameEnum.DATE_PICKER,
24 ComponentNameEnum.TIME_SELECT,
25 ComponentNameEnum.SELECT_V2
26 ]
27 if (textMap.includes(schema?.component as ComponentNameEnum)) {
28 return {
29 placeholder: '请输入'
30 }
31 }
32 if (selectMap.includes(schema?.component as ComponentNameEnum)) {
33 // 一些范围选择器
34 const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange']
35 if (
36 twoTextMap.includes(
37 ((schema?.componentProps as any)?.type ||
38 (schema?.componentProps as any)?.isRange) as string
39 )
40 ) {
41 return {
42 startPlaceholder: '开始时间',
43 endPlaceholder: '结束时间',
44 rangeSeparator: '-'
45 }
46 } else {
47 return {
48 placeholder: '请选择'
49 }
50 }
51 }
52 return {}
53 }
54
55 /**
56 *
57 * @param col 内置栅格
58 * @returns 返回栅格属性
59 * @description 合并传入进来的栅格属性
60 */
61 export const setGridProp = (col: ColProps = {}): ColProps => {
62 const colProps: ColProps = {
63 // 如果有span,代表用户优先级更高,所以不需要默认栅格
64 ...(col.span
65 ? {}
66 : {
67 xs: 24,
68 sm: 12,
69 md: 12,
70 lg: 12,
71 xl: 12
72 }),
73 ...col
74 }
75 return colProps
76 }
77
78 /**
79 *
80 * @param item 传入的组件属性
81 * @param props 传入表单所有属性
82 * @returns 默认添加 clearable 属性
83 */
84 export const setComponentProps = (item: FormSchema,props:any): Recordable => {
85 // const notNeedClearable = ['ColorPicker']
86 // 拆分事件并组合
87 const onEvents = (item?.componentProps as any)?.on || {}
88 const newOnEvents: Recordable = {}
89
90 for (const key in onEvents) {
91 if (onEvents[key]) {
92 newOnEvents[`on${firstUpperCase(key)}`] = (...args: any[]) => {
93 onEvents[key](...args)
94 }
95 }
96 }
97
98 const componentProps: Recordable = {
99 clearable: true, // 默认全局开启清空属性
100 ...item.componentProps,
101 ...newOnEvents
102 }
103 // 需要删除额外的属性
104 if (componentProps.slots) {
105 delete componentProps.slots
106 }
107 if (componentProps.on) {
108 delete componentProps.on
109 }
110 if ((item.component === 'Select' || item.component === 'Cascader' || item.component === 'TreeSelect') && (componentProps.filterable === undefined)) {
111 componentProps.filterable = true
112 }
113 if ((item.component === 'Select' || item.component === 'TreeSelect') && (componentProps.fitInputWidth === undefined)) {
114 componentProps.fitInputWidth = true
115 }
116 if ((item.component === 'InputNumber') && (componentProps.controls === undefined)) { // 默认全局取消加减号按钮
117 componentProps.controls = false
118 }
119 if ((item.component === 'InputNumber') && componentProps.precision === undefined) { // 默认全局2位小数
120 componentProps.precision = (props.disabled || componentProps.disabled) ? 3 : 2
121 }
122 if ((item.component === 'InputNumber') && componentProps.min === undefined) { // 默认默认最小值0
123 componentProps.min = 0
124 }
125 // if ((item.component === 'Input') && componentProps.formatter === undefined) { // 默认添加过滤前后空格
126 // componentProps.formatter = (value:string) => value.trim()
127 // }
128 return componentProps
129 }
130
131 /**
132 *
133 * @param formModel 表单数据
134 * @param slotsProps 插槽属性
135 */
136 export const setItemComponentSlots = (slotsProps: Recordable = {}): Recordable => {
137 const slotObj: Recordable = {}
138 for (const key in slotsProps) {
139 if (slotsProps[key]) {
140 if (typeof slotsProps[key] == 'function') {
141 slotObj[humpToDash(key)] = (...args: any[]) => {
142 return slotsProps[key]?.(...args)
143 }
144 } else {
145 slotObj[humpToDash(key)] = () => {
146 return slotsProps[key]
147 }
148 }
149 }
150 }
151 return slotObj
152 }
153
154 /**
155 *
156 * @param schema Form表单结构化数组
157 * @param formModel FormMoel
158 * @returns FormMoel
159 * @description 生成对应的formModel
160 */
161 export const initModel = (schema: FormSchema[], formModel: Recordable) => {
162 const model: Recordable = { ...formModel }
163 schema.map((v) => {
164 if (v.remove) {
165 delete model[v.field]
166 } else if (v.component !== 'Divider') {
167 // const hasField = Reflect.has(model, v.field)
168 const hasField = get(model, v.field)
169 // 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
170 set(
171 model,
172 v.field,
173 hasField !== void 0 ? get(model, v.field) : v.value !== void 0 ? v.value : undefined
174 )
175 // model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : undefined
176 }
177 })
178 return model
179 }
1 .radio_panel {
2 .el-radio {
3 &.is-bordered {
4 padding: 0;
5 position: relative;
6 border-radius:2px;
7 .el-radio__input {
8 position: absolute;
9 opacity: 0;
10 }
11
12 .el-radio__label {
13 padding: 0 15px;
14 font-size: 14px;
15 position: relative;
16
17 .corner_mark {
18 position: absolute;
19 right: 0;
20 bottom: -4px;
21 width: 18px;
22 height: 18px;
23 color: #fff;
24
25 svg {
26 width: 9px;
27 height: 9px;
28 position: absolute;
29 right: 0;
30 bottom: 2px;
31 z-index: 1;
32 }
33
34 &.active {
35 &::after {
36 content: "";
37 position: absolute;
38 border: 9px solid var(--el-color-primary);
39 border-top-color: transparent;
40 border-left-color: transparent;
41 }
42 }
43 }
44 }
45
46 .el-radio__input.is-checked+.el-radio__label {
47 color: var(--el-color-primary);
48 }
49 }
50 }
51 }
...\ No newline at end of file ...\ No newline at end of file
1 import {
2 AutocompleteProps,
3 InputNumberProps,
4 CascaderProps,
5 CascaderNode,
6 CascaderValue,
7 SwitchProps,
8 ComponentSize,
9 InputProps,
10 RateProps,
11 ColorPickerProps,
12 TransferProps,
13 RadioGroupProps,
14 RadioButtonProps,
15 CheckboxGroupProps,
16 DividerProps,
17 DatePickerProps,
18 FormItemProps as ElFormItemProps,
19 FormProps as ElFormProps,
20 ISelectProps,
21 UploadProps
22 } from 'element-plus'
23 import { IEditorConfig } from '@wangeditor/editor'
24 import { JsonEditorProps } from '@/components/JsonEditor'
25 import { CSSProperties } from 'vue'
26
27 export interface PlaceholderModel {
28 placeholder?: string
29 startPlaceholder?: string
30 endPlaceholder?: string
31 rangeSeparator?: string
32 }
33
34 export enum ComponentNameEnum {
35 RADIO_GROUP = 'RadioGroup',
36 RADIO_BUTTON = 'RadioButton',
37 CHECKBOX_GROUP = 'CheckboxGroup',
38 CHECKBOX_BUTTON = 'CheckboxButton',
39 INPUT = 'Input',
40 AUTOCOMPLETE = 'Autocomplete',
41 INPUT_NUMBER = 'InputNumber',
42 SELECT = 'Select',
43 CASCADER = 'Cascader',
44 SWITCH = 'Switch',
45 SLIDER = 'Slider',
46 TIME_PICKER = 'TimePicker',
47 DATE_PICKER = 'DatePicker',
48 RATE = 'Rate',
49 COLOR_PICKER = 'ColorPicker',
50 TRANSFER = 'Transfer',
51 DIVIDER = 'Divider',
52 TIME_SELECT = 'TimeSelect',
53 SELECT_V2 = 'SelectV2',
54 INPUT_PASSWORD = 'InputPassword',
55 EDITOR = 'Editor',
56 TREE_SELECT = 'TreeSelect',
57 UPLOAD = 'Upload',
58 JSON_EDITOR = 'JsonEditor',
59 ICON_PICKER = 'IconPicker'
60 }
61
62 type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
63 ? K extends string
64 ? K extends `${infer A}_${infer B}`
65 ? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
66 : Capitalize<Lowercase<K>>
67 : never
68 : never
69
70 export type ComponentName = CamelCaseComponentName
71
72 export interface InputPasswordComponentProps {
73 strength?: boolean
74 style?: CSSProperties
75 }
76
77 export interface InputComponentProps extends Partial<InputProps> {
78 rows?: number
79 on?: {
80 blur?: (event: FocusEvent) => void
81 focus?: (event: FocusEvent) => void
82 change?: (value: string | number) => void
83 clear?: () => void
84 input?: (value: string | number) => void
85 }
86 slots?: {
87 prefix?: (...args: any[]) => JSX.Element | null
88 suffix?: (...args: any[]) => JSX.Element | null
89 prepend?: (...args: any[]) => JSX.Element | null
90 append?: (...args: any[]) => JSX.Element | null
91 }
92 style?: CSSProperties
93 }
94
95 export interface AutocompleteComponentProps extends Partial<AutocompleteProps> {
96 on?: {
97 select?: (item: any) => void
98 change?: (value: string | number) => void
99 }
100 slots?: {
101 default?: (...args: any[]) => JSX.Element | null
102 prefix?: (...args: any[]) => JSX.Element | null
103 suffix?: (...args: any[]) => JSX.Element | null
104 prepend?: (...args: any[]) => JSX.Element | null
105 append?: (...args: any[]) => JSX.Element | null
106 }
107 style?: CSSProperties
108 }
109
110 export interface InputNumberComponentProps extends Partial<InputNumberProps> {
111 on?: {
112 change?: (currentValue: number | undefined, oldValue: number | undefined) => void
113 blur?: (event: FocusEvent) => void
114 focus?: (event: FocusEvent) => void
115 }
116 style?: CSSProperties
117 }
118
119 export interface SelectOption {
120 label?: string
121 disabled?: boolean
122 value?: any
123 key?: string | number
124 options?: SelectOption[]
125 [key: string]: any
126 }
127
128 export interface SelectComponentProps extends Omit<Partial<ISelectProps>, 'options'> {
129 /**
130 * 数据源的字段别名
131 */
132 props?: {
133 key?: string
134 value?: string
135 label?: string
136 children?: string
137 }
138 on?: {
139 change?: (value: string | number | boolean | Object) => void
140 visibleChange?: (visible: boolean) => void
141 removeTag?: (tag: any) => void
142 clear?: () => void
143 blur?: (event: FocusEvent) => void
144 focus?: (event: FocusEvent) => void
145 }
146 slots?: {
147 default?: (options: SelectOption[]) => JSX.Element[] | null
148 optionGroupDefault?: (item: SelectOption) => JSX.Element
149 optionDefault?: (option: SelectOption) => JSX.Element | null
150 prefix?: (...args: any[]) => JSX.Element | null
151 empty?: (...args: any[]) => JSX.Element | null
152 }
153 options?: SelectOption[]
154 style?: CSSProperties
155 }
156
157 export interface SelectV2ComponentProps {
158 multiple?: boolean
159 disabled?: boolean
160 valueKey?: string
161 size?: ComponentSize
162 clearable?: boolean
163 clearIcon?: string | JSX.Element | null
164 collapseTags?: boolean
165 multipleLimit?: number
166 name?: string
167 effect?: string
168 autocomplete?: string
169 placeholder?: string
170 filterable?: boolean
171 allowCreate?: boolean
172 reserveKeyword?: boolean
173 noDataText?: string
174 popperClass?: string
175 teleported?: boolean
176 persistent?: boolean
177 popperOptions?: any
178 automaticDropdown?: boolean
179 height?: number
180 scrollbarAlwaysOn?: boolean
181 remote?: boolean
182 remoteMethod?: (query: string) => void
183 validateEvent?: boolean
184 placement?: AutocompleteProps['placement']
185 collapseTagsTooltip?: boolean
186 on?: {
187 change?: (value: string | number | boolean | Object) => void
188 visibleChange?: (visible: boolean) => void
189 removeTag?: (tag: any) => void
190 clear?: () => void
191 blur?: (event: FocusEvent) => void
192 focus?: (event: FocusEvent) => void
193 }
194 options?: SelectOption[]
195 slots?: {
196 default?: (option: SelectOption) => JSX.Element | null
197 }
198 style?: CSSProperties
199 }
200
201 export interface CascaderComponentProps {
202 options?: Record<string, unknown>[]
203 props?: CascaderProps
204 size?: ComponentSize
205 placeholder?: string
206 disabled?: boolean
207 clearable?: boolean
208 showAllLevels?: boolean
209 collapseTags?: boolean
210 collapseTagsTooltip?: boolean
211 separator?: string
212 filterable?: boolean
213 filterMethod?: (node: CascaderNode, keyword: string) => boolean
214 debounce?: number
215 beforeFilter?: (value: string) => boolean
216 popperClass?: string
217 teleported?: boolean
218 tagType?: ElementPlusInfoType
219 validateEvent?: boolean
220 on?: {
221 change?: (value: CascaderValue) => void
222 expandChange?: (value: CascaderValue) => void
223 blur?: (event: FocusEvent) => void
224 focus?: (event: FocusEvent) => void
225 visibleChange?: (value: boolean) => void
226 removeTag?: (value: CascaderNode['valueByOption']) => void
227 }
228 slots?: {
229 default?: (...args: any[]) => JSX.Element | null
230 empty?: (...args: any[]) => JSX.Element | null
231 }
232 style?: CSSProperties
233 }
234
235 export interface SwitchComponentProps extends Partial<SwitchProps> {
236 on?: {
237 change?: (value: boolean | string | number) => void
238 }
239 style?: CSSProperties
240 }
241
242 export interface RateComponentProps extends Partial<RateProps> {
243 on?: {
244 change?: (value: number) => void
245 }
246 style?: CSSProperties
247 }
248
249 export interface ColorPickerComponentProps extends Partial<ColorPickerProps> {
250 on?: {
251 change?: (value: string) => void
252 activeChange?: (value: string) => void
253 }
254 style?: CSSProperties
255 }
256
257 export interface TransferComponentProps extends Partial<TransferProps> {
258 on?: {
259 change?: (
260 value: number | string,
261 direction: 'left' | 'right',
262 movedKeys: string[] | number[]
263 ) => void
264 leftCheckChange?: (value: any[]) => void
265 rightCheckChange?: (value: any[]) => void
266 }
267 slots?: {
268 default?: (...args: any[]) => JSX.Element | null
269 leftFooter?: (...args: any[]) => JSX.Element | null
270 rightFooter?: (...args: any[]) => JSX.Element | null
271 }
272 style?: CSSProperties
273 }
274
275 export interface RadioOption {
276 label?: string
277 value?: string | number | boolean
278 disabled?: boolean
279 border?: boolean
280 size?: ComponentSize
281 name?: string
282 [key: string]: any
283 }
284 export interface RadioGroupComponentProps extends Partial<RadioGroupProps> {
285 options?: RadioOption[]
286 /**
287 * 数据源的字段别名
288 */
289 props?: {
290 label?: string
291 value?: string
292 disabled?: string
293 }
294 on?: {
295 change?: (value: string | number | boolean) => void
296 }
297 slots?: {
298 default?: (...args: any[]) => JSX.Element[] | null
299 }
300 style?: CSSProperties
301 }
302
303 export interface RadioButtonComponentProps extends Partial<RadioButtonProps> {
304 options?: RadioOption[]
305 /**
306 * 数据源的字段别名
307 */
308 props?: {
309 label?: string
310 value?: string
311 disabled?: string
312 }
313 on?: {
314 change?: (value: string | number | boolean) => void
315 }
316 slots?: {
317 default?: (...args: any[]) => JSX.Element[] | null
318 }
319 style?: CSSProperties
320 }
321
322 export interface CheckboxOption {
323 label?: string
324 value?: string | number | boolean
325 disabled?: boolean
326 trueLabel?: string | number
327 falseLabel?: string | number
328 border?: boolean
329 size?: ComponentSize
330 name?: string
331 checked?: boolean
332 indeterminate?: boolean
333 validateEvent?: boolean
334 tabindex?: number | string
335 id?: string
336 controls?: boolean
337 [key: string]: any
338 }
339
340 export interface CheckboxGroupComponentProps extends Partial<CheckboxGroupProps> {
341 options?: CheckboxOption[]
342 /**
343 * 数据源的字段别名
344 */
345 props?: {
346 label?: string
347 value?: string
348 disabled?: string
349 }
350 on?: {
351 change?: (value: string | number | boolean) => void
352 }
353 slots?: {
354 default?: (...args: any[]) => JSX.Element[] | null
355 }
356 style?: CSSProperties
357 }
358
359 export interface DividerComponentProps extends Partial<DividerProps> {
360 on?: {
361 change?: (value: number) => void
362 input?: (value: number) => void
363 }
364 style?: CSSProperties
365 }
366
367 export interface DatePickerComponentProps extends Partial<DatePickerProps> {
368 on?: {
369 change?: (value: string | Date | number | string[]) => void
370 blur?: (event: FocusEvent) => void
371 focus?: (event: FocusEvent) => void
372 calendarChange?: (val: [Date, Date]) => void
373 panelChange?: (date, mode, view) => void
374 visibleChange?: (visibility: boolean) => void
375 }
376 slots?: {
377 default?: (...args: any[]) => JSX.Element | null
378 rangeSeparator?: (...args: any[]) => JSX.Element | null
379 }
380 style?: CSSProperties
381 }
382
383 export interface DateTimePickerComponentProps {
384 readonly?: boolean
385 disabled?: boolean
386 editable?: boolean
387 clearable?: boolean
388 size?: ComponentSize
389 placeholder?: string
390 startPlaceholder?: string
391 endPlaceholder?: string
392 timeArrowControl?: boolean
393 type?: 'year' | 'month' | 'date' | 'datetime' | 'datetimerange' | 'daterange' | 'week'
394 format?: string
395 popperClass?: string
396 rangeSeparator?: string
397 defaultValue?: Date | [Date, Date]
398 defaultTime?: Date | [Date, Date]
399 valueFormat?: string
400 id?: string
401 name?: string
402 unlinkPanels?: boolean
403 prefixIcon?: string | JSX.Element
404 clearIcon?: string | JSX.Element
405 shortcuts?: Array<{ text: string; value: Date | Function }>
406 disabledDate?: (date: Date) => boolean
407 cellClassName?: string | ((date: Date) => string | undefined)
408 teleported?: boolean
409 on?: {
410 change?: (value: string | Date | number | string[]) => void
411 blur?: (event: FocusEvent) => void
412 focus?: (event: FocusEvent) => void
413 calendarChange?: (val: [Date, Date]) => void
414 visibleChange?: (visibility: boolean) => void
415 }
416 slots?: {
417 default?: (...args: any[]) => JSX.Element | null
418 rangeSeparator?: (...args: any[]) => JSX.Element | null
419 }
420 style?: CSSProperties
421 }
422
423 export interface TimePickerComponentProps {
424 readonly?: boolean
425 disabled?: boolean
426 editable?: boolean
427 clearable?: boolean
428 size?: ComponentSize
429 placeholder?: string
430 startPlaceholder?: string
431 endPlaceholder?: string
432 isRange?: boolean
433 arrowControl?: boolean
434 popperClass?: string
435 rangeSeparator?: string
436 format?: string
437 defaultValue?: Date | [Date, Date]
438 id?: string
439 name?: string
440 label?: string
441 prefixIcon?: string | JSX.Element
442 clearIcon?: string | JSX.Element
443 disabledHours?: (role: string, comparingDate?: any) => number[]
444 disabledMinutes?: (hour: number, role: string, comparingDate?: any) => number[]
445 disabledSeconds?: (hour: number, minute: number, role: string, comparingDate?: any) => number[]
446 teleported?: boolean
447 tabindex?: number | string
448 on?: {
449 change: (
450 val: number | string | Date | [number, number] | [string, string] | [Date, Date]
451 ) => void
452 blur?: (event: FocusEvent) => void
453 focus?: (event: FocusEvent) => void
454 visibleChange?: (visibility: boolean) => void
455 }
456 style?: CSSProperties
457 }
458
459 export interface TimeSelectComponentProps {
460 disabled?: boolean
461 editable?: boolean
462 clearable?: boolean
463 size?: ComponentSize
464 placeholder?: string
465 name?: string
466 effect?: string
467 prefixIcon?: string | JSX.Element
468 clearIcon?: string | JSX.Element
469 start?: string
470 end?: string
471 step?: string
472 minTime?: string
473 maxTime?: string
474 format?: string
475 on?: {
476 change?: (val: string) => void
477 blur?: (event: FocusEvent) => void
478 focus?: (event: FocusEvent) => void
479 }
480 style?: CSSProperties
481 }
482
483 export interface EditorComponentProps {
484 editorConfig?: IEditorConfig
485 style?: CSSProperties
486 }
487
488 export interface ColProps {
489 span?: number
490 xs?: number
491 sm?: number
492 md?: number
493 lg?: number
494 xl?: number
495 tag?: string
496 }
497
498 export interface FormSetProps {
499 field: string
500 path: string
501 value: any
502 }
503
504 export interface FormItemProps extends Partial<ElFormItemProps> {
505 style?: CSSProperties
506 slots?: {
507 default?: (...args: any[]) => JSX.Element | null
508 label?: (...args: any[]) => JSX.Element | null
509 error?: (...args: any[]) => JSX.Element | null
510 }
511 }
512
513 export interface UploadComponentProps extends Partial<UploadProps> {
514 slots?: {
515 default?: (...args: any[]) => JSX.Element | null
516 trigger?: (...args: any[]) => JSX.Element | null
517 tip?: (...args: any[]) => JSX.Element | null
518 file?: (...args: any[]) => JSX.Element | null
519 }
520 style?: CSSProperties
521 }
522
523 export interface TreeSelectComponentProps
524 extends Omit<Partial<SelectComponentProps>, 'props' | 'on' | 'slots'> {
525 data?: any[]
526 emptyText?: string
527 nodeKey?: string
528 props?: {
529 children?: string
530 label?: string | ((...args: any[]) => string)
531 disabled?: string | ((...args: any[]) => string)
532 isLeaf?: string | ((...args: any[]) => string)
533 class?: string | ((...args: any[]) => string)
534 }
535 renderAfterExpand?: boolean
536 load?: (...args: any[]) => Promise<any>
537 renderContent?: (...args: any[]) => JSX.Element | null
538 highlightCurrent?: boolean
539 defaultExpandAll?: boolean
540 expandOnClickNode?: boolean
541 checkOnClickNode?: boolean
542 autoExpandParent?: boolean
543 defaultExpandedKeys?: any[]
544 showCheckbox?: boolean
545 checkStrictly?: boolean
546 defaultCheckedKeys?: any[]
547 currentNodeKey?: string | number
548 filterNodeMethod?: (...args: any[]) => boolean
549 accordion?: boolean
550 indent?: number
551 icon?: string | ((...args: any[]) => JSX.Element | null)
552 lazy?: boolean
553 draggable?: boolean
554 allowDrag?: (...args: any[]) => boolean
555 allowDrop?: (...args: any[]) => boolean
556 on?: {
557 change?: (value: string | number | boolean | Object) => void
558 visibleChange?: (visible: boolean) => void
559 removeTag?: (tag: any) => void
560 clear?: () => void
561 blur?: (event: FocusEvent) => void
562 focus?: (event: FocusEvent) => void
563 nodeClick?: (...args: any[]) => void
564 nodeContextMenu?: (...args: any[]) => void
565 checkChange?: (...args: any[]) => void
566 check?: (...args: any[]) => void
567 currentChange?: (...args: any[]) => void
568 nodeExpand?: (...args: any[]) => void
569 nodeCollapse?: (...args: any[]) => void
570 nodeDragStart?: (...args: any[]) => void
571 nodeDragEnter?: (...args: any[]) => void
572 nodeDragLeave?: (...args: any[]) => void
573 nodeDragOver?: (...args: any[]) => void
574 nodeDragEnd?: (...args: any[]) => void
575 nodeDrop?: (...args: any[]) => void
576 }
577 slots?: {
578 default?: (...args: any[]) => JSX.Element | null
579 optionGroupDefault?: (item: SelectOption) => JSX.Element
580 optionDefault?: (option: SelectOption) => JSX.Element | null
581 prefix?: (...args: any[]) => JSX.Element | null
582 empty?: (...args: any[]) => JSX.Element | null
583 }
584 style?: CSSProperties
585 }
586
587 export interface FormSchema {
588 /**
589 * 唯一标识
590 */
591 field: string
592 /**
593 * 表单字段类型
594 */
595 fieldType?: 'string' | 'number' | 'array' | 'date' | undefined
596
597 /**
598 * 标题
599 */
600 label?: string
601
602 /**
603 * col组件属性
604 */
605 colProps?: ColProps
606
607 /**
608 * 表单组件属性,具体可以查看element-plus文档
609 */
610 componentProps?:
611 | InputComponentProps
612 | AutocompleteComponentProps
613 | InputNumberComponentProps
614 | SelectComponentProps
615 | SelectV2ComponentProps
616 | CascaderComponentProps
617 | SwitchComponentProps
618 | RateComponentProps
619 | ColorPickerComponentProps
620 | TransferComponentProps
621 | RadioGroupComponentProps
622 | RadioButtonComponentProps
623 | DividerComponentProps
624 | DatePickerComponentProps
625 | DateTimePickerComponentProps
626 | TimePickerComponentProps
627 | InputPasswordComponentProps
628 | TreeSelectComponentProps
629 | UploadComponentProps
630 | JsonEditorProps
631 | any
632
633 /**
634 * formItem组件属性,具体可以查看element-plus文档
635 */
636 formItemProps?: FormItemProps
637
638 /**
639 * 渲染的组件名称
640 */
641 component?: ComponentName
642
643 /**
644 * 初始值
645 */
646 value?: any
647
648 /**
649 * 是否隐藏,如果为true,会连同值一同删除,类似v-if
650 */
651 remove?: boolean
652 /**
653 * 是否显示,如果为false,类似v-if
654 */
655 visible?: boolean
656
657 /**
658 * 样式隐藏,不会把值一同删掉,类似v-show
659 */
660 hidden?: boolean
661
662 /**
663 * @returns 远程加载下拉项
664 */
665 optionApi?: any
666 }
667
668 export interface FormProps extends Partial<ElFormProps> {
669 schema?: FormSchema[]
670 isCol?: boolean
671 model?: Recordable
672 autoSetPlaceholder?: boolean
673 isCustom?: boolean
674 [key: string]: any
675 }
...\ No newline at end of file ...\ No newline at end of file
1 <template>
2 <div>
3 <DialogPlus modal-class="auth-user" append-to-body v-model="dialogVisible" @open="openedDialog" width="460px"
4 maxHeight="250px" :close-on-click-modal="false" title="用户身份认证">
5 <div class="select-tenant">
6 <el-form v-show="formType == 'validate'" label-position="top" ref="registerFormRef" :model="registerForm"
7 :rules="registerRules" class="login-form" auto-complete="on">
8 <el-form-item prop="logonUser" label="账号">
9 <el-input v-model.trim="registerForm.logonUser" :disabled="isModifypassword" placeholder="请输入账号" text tabindex="1" autocomplete="on">
10 </el-input>
11 </el-form-item>
12 <el-form-item prop="mobileNo" label="手机号">
13 <el-input v-model.trim="registerForm.mobileNo" :disabled="isModifypassword" placeholder="请输入手机号" text tabindex="1" autocomplete="on">
14 </el-input>
15 </el-form-item>
16 <el-form-item prop="validateCode" label="图形验证码">
17 <el-input class="captcha" v-model.trim="registerForm.validateCode" placeholder="请输入图形验证码" tabindex="2"
18 autocomplete="on">
19 <template #append>
20 <img class="h-26px" :src="imgCaptchaBase64" @click="refreshPictureCode" />
21 </template>
22 </el-input>
23 </el-form-item>
24 </el-form>
25 <el-form v-show="formType === 'reset'" label-position="top" ref="resetFormRef" :model="resetForm" :rules="resetRules"
26 autocomplete="off" class="login-form" auto-complete="on">
27 <el-form-item prop="smsCode" autocomplete="off" label="短信验证码">
28 <el-input v-model.trim="resetForm.smsCode" autocomplete="off" name="captcha-unique123"
29 :placeholder="`请输入${registerForm.mobileNo.substring(0, 3)}****${registerForm.mobileNo.substring(7)}收到的短信验证码`">
30 <template #append>
31 <span>{{ `${timeLeft}s` }}</span>
32 </template>
33 </el-input>
34 <div class="code-desc">
35 <span>若手机未收到验证码,请</span>
36 <el-button link type="primary" size="small" @click="recertification"
37 style="padding: 0px;vertical-align: baseline;">重新认证</el-button>
38 </div>
39 </el-form-item>
40 <el-form-item prop="pwd" label="新密码" :error="errorPsw">
41 <!-- <el-input v-model.trim="resetForm.pwd" type="password" placeholder="请输入新密码" show-password autocomplete="off"
42 autocomplete="new-password">
43 </el-input> -->
44 <PasswordStrengthMeter v-model.trim="password" placeholder="请输入新密码" @change="changePwd" style="width: 100%;"></PasswordStrengthMeter>
45 </el-form-item>
46 <el-form-item prop="checkPwd" label="确认新密码">
47 <el-input v-model.trim="resetForm.checkPwd" type="password" placeholder="请再次输入新密码" show-password
48 autocomplete="new-password">
49 </el-input>
50 </el-form-item>
51 </el-form>
52 </div>
53 <template #footer>
54 <el-button @click="closeDialog"> </el-button>
55 <el-button v-show="formType === 'validate'" :loading="sendCodeLoading" type="primary"
56 @click="handleSendCaptcha">发送验证码至手机</el-button>
57 <el-button v-show="formType === 'reset' && timeLeft > 0" :loading="saveLoading" type="primary"
58 :disabled="!resetForm.smsCode || !resetForm.pwd || !resetForm.checkPwd"
59 @click.prevent="handleReset">修改密码</el-button>
60 <el-button v-show="formType === 'reset' && timeLeft == 0" :loading="saveLoading" type="primary"
61 @click.prevent="recertification">重新认证</el-button>
62 </template>
63 </DialogPlus>
64 </div>
65 </template>
66
67 <script lang="ts" setup>
68 import { useValidator } from '@/hooks/useValidator';
69 import useCountdown from '@/hooks/useCountdown'
70 import type { FormRules } from 'element-plus'
71 import { propTypes } from '@/utils/propTypes'
72 import useIdaasStore from '@/store/modules/idaas';
73 import {
74 getPictureCode
75 } from "@/api/modules/idaas"
76 import useLogin from '@/store/modules/login'
77 import PasswordStrengthMeter from '../PasswordStrengthMeter/index.vue'
78 import { autoSalt } from '@/utils/common';
79
80 // const AsyncPasswordStrengthMeter = defineAsyncComponent(() =>
81 // import('../PasswordStrengthMeter/index.vue')
82 // );
83
84 const loginStore = useLogin()
85 const idaasStore = useIdaasStore()
86 const { required, phone } = useValidator();
87 const { proxy } = getCurrentInstance() as any;
88 const mobileNo = computed(()=> idaasStore.idaasUserInfo.principal?.mobileNo || '')
89 const logonUser = computed(()=> idaasStore.idaasUserInfo.principal?.logonUser || '')
90
91 const props = defineProps({
92 schemaInfo: {
93 type: Object as PropType<{
94 visible: boolean;
95 }>,
96 default: () => { }
97 },
98 operate:propTypes.string.def('modifypassword'),
99 });
100
101 const isModifypassword = computed(() => props.operate === 'modifypassword')
102
103 const dialogVisible = computed({
104 get: () => {
105 return props.schemaInfo.visible;
106 },
107 set: (val) => {
108 props.schemaInfo.visible = val
109 }
110 })
111
112 function openedDialog() {
113 recertification()
114 getPictureCodeInfo()
115 }
116
117 function closeDialog() {
118 dialogVisible.value = false
119 }
120
121 // 重新认证
122 const recertification = () => {
123 formType.value = 'validate';
124 clearForm()
125 reset()
126 }
127
128 function clearForm() {
129 let _mobileNo = isModifypassword.value ? mobileNo.value : ''
130 let _logonUser = isModifypassword.value ? logonUser.value : ''
131 password.value = ''
132 resetForm.value = {
133 smsCode: '',
134 pwd: '',
135 checkPwd: ''
136 }
137 registerForm.value = {
138 logonUser: _logonUser,
139 mobileNo: _mobileNo,
140 validateCode: ''
141 }
142 }
143
144 // ! 修改密码逻辑
145 const formType = ref('validate')
146 const resetFormRef = ref()
147 const registerFormRef = ref()
148
149
150
151 const registerRules = ref<FormRules>({
152 logonUser: [
153 { required: true, trigger: 'blur', message: '请输入账号' },
154 ],
155 mobileNo: [
156 required('请输入手机号'), phone()
157 ],
158 validateCode: [
159 { required: true, trigger: 'blur', message: '请输入图形验证码' },
160 ]
161 })
162 const registerForm = ref({
163 logonUser: '',
164 mobileNo: '',
165 validateCode: ''
166 })
167
168 function changePwd() {
169 resetForm.value.pwd = password.value
170 }
171
172
173 const errorPsw = computed(() => loginStore.firstUnmetRequirement ? `需要${loginStore.firstUnmetRequirement}` : null)
174 const password = ref('')
175 /**
176 * 校验新密码
177 */
178 function validatorPassword(rule, value, callback) {
179 console.log(value,loginStore.firstUnmetRequirement);
180 if (loginStore.firstUnmetRequirement) {
181 callback(new Error(`需要${loginStore.firstUnmetRequirement}`))
182 } else {
183 callback();
184 }
185 }
186
187 /**
188 * 校验确认密码
189 */
190 function validatorConfirmpwd(rule, value, callback) {
191 if (value != resetForm.value.pwd) {
192 callback(new Error('密码不一致,请重新输入'))
193 } else {
194 callback();
195 }
196 }
197
198 const resetRules = ref<FormRules>({
199 smsCode: [
200 // 不显示验证信息了,只有填写完整,保存按钮才会被使用。
201 // {required: true, trigger: 'blur', message: '请输入收到的短信验证码' },
202 ],
203 pwd: [
204 {
205 validator: validatorPassword,
206 trigger: []
207 }
208 ],
209 checkPwd: [
210 {
211 validator: validatorConfirmpwd,
212 trigger: ['change', 'blur']
213 }
214 ]
215 })
216 const resetForm = ref({
217 smsCode: '',
218 pwd: '',
219 checkPwd: ''
220 })
221
222
223
224
225 let promise: any = ref(null);
226 /** 图形验证码图片。 */
227 const imgCaptchaBase64 = ref('');
228 /** 图形验证码guid */
229 const validateCodeGuid = ref('');
230
231 /** 发送短信验证码的loading */
232 const sendCodeLoading = ref(false);
233 /** 重置密码保存的loading */
234 const saveLoading = ref(false);
235
236
237 const { timeLeft, minutes, seconds, start, stop, reset } = useCountdown(300);
238 watchEffect(() => {
239 console.log(timeLeft.value, 'timeLeft');
240 if (timeLeft.value == 0) {
241 stop()
242 }
243 })
244
245
246 const refreshPictureCode = () => {
247 if (promise.value) {
248 return;
249 }
250 getPictureCodeInfo()
251 }
252
253 function getPictureCodeInfo() {
254 promise.value = getPictureCode().then((res: any) => {
255 promise.value = null;
256 if (res.data.code == proxy.$passCode) {
257 imgCaptchaBase64.value = res.data.data?.imageBase64 || "";
258 validateCodeGuid.value = res.data.data?.guid || "";
259 }
260 });
261 }
262
263 const handleSendCaptcha = () => {
264 registerFormRef.value && registerFormRef.value.validate((valid) => {
265 if (valid) {
266 sendCodeLoading.value = true
267 let params: any = { ...registerForm.value };
268 params.validateCodeGuid = validateCodeGuid.value;
269 idaasApi.sendCode(params).then((res: any) => {
270 sendCodeLoading.value = false;
271 if (res?.data.code == proxy.$passCode) {
272 proxy.$ElMessage.success('验证码发送成功!');
273 loginStore.firstUnmetRequirement = ''
274 formType.value = 'reset';
275 resetForm.value.smsCode = '';
276 resetForm.value.pwd = "";
277 resetForm.value.checkPwd = "";
278 start()
279 } else {
280 refreshPictureCode();
281 registerForm.value.validateCode = "";
282 }
283 });
284 }
285 });
286 }
287
288 async function handleReset() {
289 let res = await resetFormRef.value?.validate()
290 if (!res) return
291 let params = Object.assign({}, resetForm.value, { mobileNo: registerForm.value.mobileNo, logonUser: registerForm.value.logonUser });
292 params.pwd = autoSalt(params.pwd, false, false);
293 idaasApi.resetPwd(params).then((res: any) => {
294 if (res.data.code == proxy.$passCode) {
295 proxy.$ElMessage.success('密码重置成功');
296 dialogVisible.value = false;
297 userApi.recordUpdateTime(registerForm.value.logonUser)
298 }
299 });
300 }
301
302
303 </script>
...\ No newline at end of file ...\ No newline at end of file
1 const lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2 const reverseLookup = new Uint8Array(256);
3
4 // 初始化 reverseLookup 数组
5 for (let i = 0; i < lookup.length; i++) {
6 reverseLookup[lookup.charCodeAt(i)] = i;
7 }
8
9 function decodeBase64url(base64url) {
10 var base64urlLength = base64url.length;
11
12 var placeHolderLength = base64url.charAt(base64urlLength - 2) === '=' ? 2 : base64url.charAt(base64urlLength - 1) === '=' ? 1 : 0;
13 var bufferLength = (base64urlLength * 3 / 4) - placeHolderLength;
14
15 var arrayBuffer = new ArrayBuffer(bufferLength);
16 var uint8Array = new Uint8Array(arrayBuffer);
17
18 var j = 0;
19 for (var i = 0; i < base64urlLength; i+=4) {
20 var tmp0 = reverseLookup[base64url.charCodeAt(i)];
21 var tmp1 = reverseLookup[base64url.charCodeAt(i+1)];
22 var tmp2 = reverseLookup[base64url.charCodeAt(i+2)];
23 var tmp3 = reverseLookup[base64url.charCodeAt(i+3)];
24
25 uint8Array[j++] = (tmp0 << 2) | (tmp1 >> 4);
26 uint8Array[j++] = ((tmp1 & 15) << 4) | (tmp2 >> 2);
27 uint8Array[j++] = ((tmp2 & 3) << 6) | (tmp3 & 63);
28 }
29
30 return arrayBuffer;
31 }
32
33 function encodeBase64url(arrayBuffer) {
34 var uint8Array = new Uint8Array(arrayBuffer);
35 var length = uint8Array.length;
36 var base64url = "";
37
38 for (var i = 0; i < length; i+=3) {
39 base64url += lookup[uint8Array[i] >> 2];
40 base64url += lookup[((uint8Array[i] & 3) << 4) | (uint8Array[i + 1] >> 4)];
41 base64url += lookup[((uint8Array[i + 1] & 15) << 2) | (uint8Array[i + 2] >> 6)];
42 base64url += lookup[uint8Array[i + 2] & 63];
43 }
44
45 switch (length % 3) {
46 case 1:
47 base64url = base64url.substring(0, base64url.length - 2);
48 break;
49 case 2:
50 base64url = base64url.substring(0, base64url.length - 1);
51 break;
52 }
53 return base64url;
54 }
55
56 // 导出函数
57 export default { decodeBase64url, encodeBase64url };
...\ No newline at end of file ...\ No newline at end of file
1 import { ref, computed } from 'vue';
2
3 /**
4 * 创建一个倒计时的自定义组合函数。
5 *
6 * @param {number} duration - 倒计时的初始秒数,默认为60秒。
7 * @returns {object} 包含倒计时状态和控制方法的对象。
8 */
9 export default function useCountdown(duration = 60) {
10 // 使用 ref 创建响应式的倒计时剩余时间
11 const timeLeft = ref(duration);
12
13 // 用于存储定时器 ID 的变量
14 let intervalId = null;
15
16 // 计算属性,将剩余时间转换为分钟和秒数
17 const minutes = computed(() => Math.floor(timeLeft.value / 60));
18 const seconds = computed(() =>
19 ('0' + (timeLeft.value % 60)).slice(-2) // 确保秒数始终显示两位数字
20 );
21
22 /**
23 * 开始倒计时。
24 */
25 function start() {
26 if (!intervalId && timeLeft.value > 0) {
27 // 设置每秒减少一次剩余时间的定时器
28 intervalId = setInterval(() => {
29 if (timeLeft.value <= 0) {
30 clearInterval(intervalId); // 当时间用完时清除定时器
31 intervalId = null;
32 return;
33 }
34 timeLeft.value--; // 每秒减少一秒
35 }, 1000);
36 }
37 }
38
39 /**
40 * 停止倒计时。
41 */
42 function stop() {
43 if (intervalId) {
44 clearInterval(intervalId); // 清除定时器以停止倒计时
45 intervalId = null;
46 }
47 }
48
49 /**
50 * 重置倒计时到初始值或指定的新持续时间。
51 *
52 * @param {number} [newDuration=duration] - 新的倒计时持续时间(秒),可选。
53 */
54 function reset(newDuration = duration) {
55 stop(); // 先停止当前倒计时
56 timeLeft.value = newDuration; // 重置剩余时间为新的持续时间或默认持续时间
57 }
58
59 // 返回包含倒计时状态和控制方法的对象
60 return {
61 timeLeft, // 剩余时间(秒)
62 minutes, // 分钟
63 seconds, // 秒
64 start, // 开始
65 stop, // 暂停
66 reset // 重置
67 };
68 }
...\ No newline at end of file ...\ No newline at end of file
1 import type { FormPlus, FormExpose } from '@/components/FormPlus'
2 import type { ElForm, ElFormItem } from 'element-plus'
3 import { ref, unref, nextTick } from 'vue'
4 import { FormSchema, FormSetProps, FormProps } from '@/components/FormPlus'
5 import { isEmptyVal, isObject } from '@/utils/common'
6 import { cloneDeep } from 'lodash-es'
7 import { ComponentRef } from '@/types/global'
8
9 export const useForm = () => {
10 // From实例
11 const formRef = ref<typeof FormPlus & FormExpose>()
12
13 // ElForm实例
14 const elFormRef = ref<ComponentRef<typeof ElForm>>()
15
16 /**
17 * @param ref Form实例
18 * @param elRef ElForm实例
19 */
20 const register = (ref: typeof FormPlus & FormExpose, elRef: ComponentRef<typeof ElForm>) => {
21 formRef.value = ref
22 elFormRef.value = elRef
23 }
24
25 const getForm = async () => {
26 await nextTick()
27 const form = unref(formRef)
28 if (!form) {
29 console.error('The form is not registered. Please use the register method to register')
30 }
31 return form
32 }
33
34 /**
35 * !数据类型空值映射
36 */
37 const defaultEmptyValues = (fieldType:string | undefined):'' | any[] | null => {
38 let map = {
39 'string':'',
40 'number':'',
41 'array':[],
42 'date':null
43 }
44 return fieldType ? map[fieldType] : ''
45 }
46 // 一些内置的方法
47 const methods = {
48 /**
49 * @description 设置form组件的props
50 * @param props form组件的props
51 */
52 setProps: async (props: FormProps = {}) => {
53 const form = await getForm()
54 form?.setProps(props)
55 if (props.model) {
56 form?.setValues(props.model)
57 }
58 },
59
60 /**
61 * @description 设置form的值
62 * @param data 需要设置的数据
63 */
64 setValues: async (data: any) => {
65 const form = await getForm()
66 form?.setValues(data)
67 },
68
69 /**
70 * @description 设置schema
71 * @param schemaProps 需要设置的schemaProps
72 */
73 setSchema: async (schemaProps: FormSetProps[]) => {
74 const form = await getForm()
75 form?.setSchema(schemaProps)
76 },
77
78 /**
79 * @description 新增schema
80 * @param formSchema 需要新增数据
81 * @param index 在哪里新增
82 */
83 addSchema: async (formSchema: FormSchema, index?: number) => {
84 const form = await getForm()
85 form?.addSchema(formSchema, index)
86 },
87
88 /**
89 * @description 新增schema
90 * @param formSchemas 需要新增数据
91 * @param index 在哪里新增
92 */
93 addSchemas: async (formSchemas: FormSchema[], index?: number) => {
94 const form = await getForm()
95 form?.addSchemas(formSchemas, index)
96 },
97
98 /**
99 * @description 删除schema
100 * @param field 删除哪个数据
101 * @param deleteField 是否删除字段
102 */
103 delSchema: async (field: string,deleteField:boolean = true) => {
104 const form = await getForm()
105 form?.delSchema(field,deleteField)
106 },
107
108 /**
109 * @description 获取表单数据
110 * @returns form data
111 */
112 getFormData: async (filterEmptyVal = true): Promise<any> => {
113 const form = await getForm()
114 const model = form?.formModel as any
115 if (filterEmptyVal) {
116 // 使用reduce过滤空值,并返回一个新对象
117 return Object.keys(model).reduce((prev, next) => {
118 const value = model[next]
119 if (!isEmptyVal(value)) {
120 if (isObject(value)) {
121 if (Object.keys(value).length > 0) {
122 prev[next] = value
123 }
124 } else {
125 prev[next] = value
126 }
127 }
128 return prev
129 }, {}) as T
130 } else {
131 return model as T
132 }
133 },
134
135 /**
136 * @description 获取表单组件的实例
137 * @param field 表单项唯一标识
138 * @returns component instance
139 */
140 getComponentExpose: async (field: string) => {
141 const form = await getForm()
142 return form?.getComponentExpose(field)
143 },
144
145 /**
146 * @description 获取formItem组件的实例
147 * @param field 表单项唯一标识
148 * @returns formItem instance
149 */
150 getFormItemExpose: async (field: string) => {
151 const form = await getForm()
152 return form?.getFormItemExpose(field) as ComponentRef<typeof ElFormItem>
153 },
154
155 /**
156 * @description 获取ElForm组件的实例
157 * @returns ElForm instance
158 */
159 getElFormExpose: async () => {
160 await getForm()
161 return unref(elFormRef)
162 },
163
164 getFormExpose: async () => {
165 await getForm()
166 return unref(formRef)
167 },
168 /**
169 * 根据schema拿到表单的完整数据(如果字段为空则返回空字符串)
170 * @param resultForm
171 * @param schemaParam
172 * @returns
173 */
174 getFormResult: (resultForm, schemaParam: FormSchema[]) => {
175 let res = {}
176 schemaParam.forEach(item => {
177 res[item.field] = isEmptyVal(resultForm[item.field]) ? defaultEmptyValues(item.fieldType) : resultForm[item.field]
178 })
179 return res
180 }
181 }
182
183 return {
184 formRegister: register,
185 formMethods: methods
186 }
187 }
188
189 /**
190 *
191 * @param reserveKeys 需要保留的key
192 * @param obj 需要删除的对象
193 * @description 删除对象中的key
194 */
195 export const reserveField = (reserveKeys: string[],obj) => {
196 let copy = cloneDeep(obj)
197 for (const key in copy) {
198 if (reserveKeys.indexOf(key) === -1) {
199 delete copy[key]
200 }
201 }
202 return copy
203 }
1 import dayjs from '@/utils/dayjs'
2 import useUserStore from '@/store/modules/user'
3 1
4 import { getOrganisationRelTreeListPromise, getTemplateListPromise } from "@/api/modules/dataBasic" 2 import { getOrganisationRelTreeListPromise, getTemplateListPromise } from "@/api/modules/dataBasic"
5 3
6 const currentDate = dayjs(new Date()).format('YYYY-MM-DD')
7
8 const isArray = (val: any): val is Array<any> => { 4 const isArray = (val: any): val is Array<any> => {
9 return val && Array.isArray(val) 5 return val && Array.isArray(val)
10 } 6 }
...@@ -15,81 +11,8 @@ const isNonEmptyArray = (val: any): boolean => { ...@@ -15,81 +11,8 @@ const isNonEmptyArray = (val: any): boolean => {
15 11
16 const useGetData = (param = {}) => { 12 const useGetData = (param = {}) => {
17 const BasicInfo: any = ref({}) // 基础资料 13 const BasicInfo: any = ref({}) // 基础资料
18 const gradeList = ref<any>([]) // 职级关系
19 const platformGradeList = ref<any>([]) // 平台职级
20 const postionList = ref<any>([]) // 职位
21 const tenantRelList = ref<any>([]) // 公司关系
22 const templateList = ref<any>([]) // 菜单模板列表 14 const templateList = ref<any>([]) // 菜单模板列表
23 const orgMap = ref<orgMapRes>() // 人员组织信息
24 const organisationTree = ref<any>([]) // 组织树 15 const organisationTree = ref<any>([]) // 组织树
25 const personelTree = ref<any>([]) // 人员树
26 const amoebaTree = ref<any>([]) // 阿米巴树
27 const financeSubjectDict = ref<any>([]) // 关联财务科目字典
28 const systemSideList = ref<{ // 系统列表
29 systemName: string,
30 guid: string
31 }[]>([])
32 const getFinanceSubject = async (customParam = {}) => { // 获取业务线
33 return await budgetApi.getFinanceSubjectTreePromise((Object.assign({}, param, customParam)))
34 }
35 // !基础数据缓存机制
36 async function getFinanceSubjectTree({ useCache = true, customParam = {} } = {}) {
37 if (isNonEmptyArray(BasicInfo.financeSubject) && useCache) {
38 return BasicInfo.financeSubject
39 } else {
40 let res = await getFinanceSubject(customParam)
41 BasicInfo.financeSubject = useCache ? res : []
42 return res
43 }
44 }
45 async function getGradeTitleRelList({ useCache = true, customParam = {} } = {}) { // 获取职级关系列表
46 if ( isNonEmptyArray(BasicInfo.gradeList) && useCache) {
47 gradeList.value = BasicInfo.gradeList
48 return gradeList.value
49 } else {
50 let res = await userApi.getGradeRelListPromise(customParam)
51 BasicInfo.gradeList = useCache ? res : []
52 gradeList.value = res
53 return gradeList.value
54 }
55 }
56
57 async function getGradeList({ useCache = true, customParam = {} } = {}) { // 获取平台职级列表
58 if ( isNonEmptyArray(BasicInfo.platformGradeList) && useCache) {
59 platformGradeList.value = BasicInfo.platformGradeList
60 return platformGradeList.value
61 } else {
62 let res = await userApi.getGradeListPromise(customParam)
63 BasicInfo.platformGradeList = useCache ? res : []
64 platformGradeList.value = res
65 return platformGradeList.value
66 }
67 }
68
69 async function getPostionList({ useCache = true, customParam = {} } = {}) { // 获取职位
70 if ( isNonEmptyArray(BasicInfo.postionList) && useCache) {
71 postionList.value = BasicInfo.postionList
72 return postionList.value
73 } else {
74 let res = await userApi.getPositionPromise(customParam)
75 BasicInfo.postionList = useCache ? res : []
76 postionList.value = res
77 return postionList.value
78 }
79 }
80
81 async function getTenantRelList(useCache = true) { // 获取公司关系列表
82 if (isNonEmptyArray(BasicInfo.tenantRelList) && useCache) {
83 tenantRelList.value = BasicInfo.tenantRelList
84 return tenantRelList.value
85 } else {
86 let res = await tenantApi.getTenantListPromise('Y')
87 BasicInfo.tenantRelList = useCache ? res : []
88 tenantRelList.value = res
89 return tenantRelList.value
90 }
91 }
92
93 async function getPermissionTemplateList({ useCache = true, customParam = {} } = {}) { // 获取菜单模板 16 async function getPermissionTemplateList({ useCache = true, customParam = {} } = {}) { // 获取菜单模板
94 if (isNonEmptyArray(templateList.value) && useCache) { 17 if (isNonEmptyArray(templateList.value) && useCache) {
95 return templateList.value 18 return templateList.value
...@@ -100,47 +23,6 @@ const useGetData = (param = {}) => { ...@@ -100,47 +23,6 @@ const useGetData = (param = {}) => {
100 } 23 }
101 } 24 }
102 25
103 interface orgMapRes {
104 [key:string]:{
105 guid:string, // staffGuid
106 userGuid:string, // userGuid
107 orgGuid:string, // 部门
108 orgName:string,
109 orgGuidTop:string, // 一级部门guid
110 orgNameTop:number,
111 }
112 }
113 // 获取人员组织信息
114 async function getOrgMap({ useCache = true, customParam = {} } = {}) { // 获取人员组织信息
115 const userStore = useUserStore()
116 let staffGuid = userStore.userInfo.staffGuid
117 if (isObject(BasicInfo.orgMap) && BasicInfo.orgMap[staffGuid] && useCache) {
118 orgMap.value = BasicInfo.orgMap
119 return orgMap.value
120 } else {
121 const userStore = useUserStore()
122 let res = await staffApi.getOrgMap([userStore.userInfo.staffGuid])
123 BasicInfo.orgMap = useCache ? res : null
124 orgMap.value = res
125 return orgMap.value
126 }
127 }
128
129 // 获取组织树
130 async function getPersonelTree({ useCache = true, customParam = {} } = {}) {
131 if ( isNonEmptyArray(BasicInfo.personelTree) && useCache) {
132 personelTree.value = BasicInfo.personelTree
133 return personelTree.value
134 } else {
135 const userStore = useUserStore()
136 let res = await tenantApi.getOrganisationTreePromise(userStore.userInfo.tenantGuid)
137 mulTreeData(res)
138 BasicInfo.personelTree = useCache ? res : []
139 personelTree.value = res
140 return personelTree.value
141 }
142 }
143
144 async function getOrganisationTree({ useCache = true, tenantGuid = '' } = {}) { // 获取组织树 26 async function getOrganisationTree({ useCache = true, tenantGuid = '' } = {}) { // 获取组织树
145 if ( isNonEmptyArray(BasicInfo.organisationTree) && useCache) { 27 if ( isNonEmptyArray(BasicInfo.organisationTree) && useCache) {
146 organisationTree.value = BasicInfo.organisationTree 28 organisationTree.value = BasicInfo.organisationTree
...@@ -154,59 +36,9 @@ const useGetData = (param = {}) => { ...@@ -154,59 +36,9 @@ const useGetData = (param = {}) => {
154 } 36 }
155 } 37 }
156 38
157 async function getAmoebaTree({ useCache = true, customParam = {} } = {}) { // 获取组织树
158 if ( isNonEmptyArray(BasicInfo.amoebaTree) && useCache) {
159 amoebaTree.value = BasicInfo.amoebaTree
160 return amoebaTree.value
161 } else {
162 let res = await tenantApi.getAmoebaTreePromise2(currentDate)
163 getOrgtreeData(res,false)
164 BasicInfo.amoebaTree = useCache ? res : []
165 amoebaTree.value = res
166 return amoebaTree.value
167 }
168 }
169
170 async function getFinanceDict({ useCache = true, customParam = {} } = {}) { // 获取财务科目关联字典
171 if ( isNonEmptyArray(BasicInfo.financeSubjectDict) && useCache) {
172 financeSubjectDict.value = BasicInfo.financeSubjectDict
173 return financeSubjectDict.value
174 } else {
175 let res = await budgetApi.getSubjectDict(customParam)
176 getOrgtreeData(res,false)
177 BasicInfo.financeSubjectDict = useCache ? res : []
178 financeSubjectDict.value = res
179 return financeSubjectDict.value
180 }
181 }
182
183 // 获取子系统列表
184 async function getSystemSideList({ useCache = true, customParam = {} } = {}) {
185 if ( isNonEmptyArray(BasicInfo.systemSideList) && useCache) {
186 systemSideList.value = BasicInfo.systemSideList
187 return systemSideList.value
188 } else {
189 let res = await authApi.getSystemSideData()
190 BasicInfo.systemSideList = useCache ? res : []
191 systemSideList.value = res
192 return systemSideList.value
193 }
194 }
195
196 return { 39 return {
197 getPersonelTree,
198 getFinanceSubject,
199 getGradeTitleRelList,
200 getGradeList,
201 getPostionList,
202 getTenantRelList,
203 getFinanceSubjectTree,
204 getPermissionTemplateList, 40 getPermissionTemplateList,
205 getOrgMap,
206 getOrganisationTree, 41 getOrganisationTree,
207 getAmoebaTree,
208 getFinanceDict,
209 getSystemSideList
210 } 42 }
211 } 43 }
212 44
......
...@@ -11,7 +11,6 @@ import useKeepAliveStore from '@/store/modules/keepAlive' ...@@ -11,7 +11,6 @@ import useKeepAliveStore from '@/store/modules/keepAlive'
11 import useUserStore from '@/store/modules/user' 11 import useUserStore from '@/store/modules/user'
12 import useMenuStore from '@/store/modules/menu' 12 import useMenuStore from '@/store/modules/menu'
13 import useRouteStore from '@/store/modules/route' 13 import useRouteStore from '@/store/modules/route'
14 import route from '@/mock/route'
15 14
16 const { isLoading } = useNProgress() 15 const { isLoading } = useNProgress()
17 16
...@@ -125,19 +124,19 @@ router.beforeEach(async (to, from, next) => { ...@@ -125,19 +124,19 @@ router.beforeEach(async (to, from, next) => {
125 } 124 }
126 } 125 }
127 else { 126 else {
128 if (to.name === 'home' || to.name == 'contactInfo' || to.name == 'register' || to.name == 'registerMobile' || to.name == 'homeDamRegister' || to.name == 'homeDamRegisterMobile' || to.name == 'homeDamFinance' || to.name == 'homeDamDataCircule' || 127 if (to.name == 'userPrivate' || to.name == 'userAgree' || to.name === 'home' || to.name == 'contactInfo' || to.name == 'register' || to.name == 'registerMobile' || to.name == 'homeDamRegister' || to.name == 'homeDamRegisterMobile' || to.name == 'homeDamFinance' || to.name == 'homeDamDataCircule' ||
129 to.name == 'homeDamDemand' || to.name == 'homeDamAlgorithm' || to.name == 'homeDamAlgorithmMobile' || to.name == 'homeDamMarket' || to.name == 'homeDamMarketMobile' 128 to.name == 'homeDamDemand' || to.name == 'homeDamAlgorithm' || to.name == 'homeDamAlgorithmMobile' || to.name == 'homeDamMarket' || to.name == 'homeDamMarketMobile'
130 || to.name == 'homeDamDataCirculeMobile' || to.name == 'homeDamDemandMobile' || to.name == 'homeDamFinanceMobile') { 129 || to.name == 'homeDamDataCirculeMobile' || to.name == 'homeDamDemandMobile' || to.name == 'homeDamFinanceMobile') {
131 next() 130 next()
132 } 131 }
133 else if (!to.query.code && to.name !== 'login') { 132 else if (!to.query.code && to.name !== 'login') {
134 window.location.href = import.meta.env.VITE_IDASS_BASEURL; 133 // window.location.href = import.meta.env.VITE_IDASS_BASEURL;
135 // next({ 134 next({
136 // name: 'login', 135 name: 'login',
137 // query: { 136 query: {
138 // redirect: to.fullPath !== '/' ? to.fullPath : undefined, 137 redirect: to.fullPath !== '/' ? to.fullPath : undefined,
139 // }, 138 },
140 // }) 139 })
141 } 140 }
142 else { 141 else {
143 next() 142 next()
......
...@@ -26,14 +26,30 @@ interface metaInfoRaw { ...@@ -26,14 +26,30 @@ interface metaInfoRaw {
26 26
27 // 固定路由(默认路由) 27 // 固定路由(默认路由)
28 const constantRoutes: RouteRecordRaw[] = [ 28 const constantRoutes: RouteRecordRaw[] = [
29 // { 29 {
30 // path: '/login', 30 path: '/portalLogin',
31 // name: 'login', 31 name: 'login',
32 // component: () => import('@/views/login.vue'), 32 component: () => import('@/views/portal/portalLogin.vue'),
33 // meta: { 33 meta: {
34 // title: '登录', 34 title: '登录',
35 // }, 35 },
36 // }, 36 },
37 {
38 path: '/userPrivate',
39 name: 'userPrivate',
40 component: () => import('@/views/portal/userPrivate.vue'),
41 meta: {
42 title: '隐私声明',
43 },
44 },
45 {
46 path: '/userAgree',
47 name: 'userAgree',
48 component: () => import('@/views/portal/userAgree.vue'),
49 meta: {
50 title: '用户协议',
51 },
52 },
37 { 53 {
38 path: '/:all(.*)*', 54 path: '/:all(.*)*',
39 name: 'notFound', 55 name: 'notFound',
......
1
2 import { idaasLogin } from '@/api/modules/idaas';
3 import { autoSalt } from '@/utils/common';
4 import { ElMessage } from 'element-plus';
5
6
7
8 const useIdaas = defineStore(
9 // 唯一ID
10 'idaas',
11 () => {
12 const idaasToken = ref('') // idaas token
13 const isLoginOut = ref(false);// idaas 退出登录。
14 const idaasUserInfo = ref<{
15 principal?:{
16 logonUser:string,
17 mobileNo:string,
18 name:string
19 }
20 }>({})
21
22 // 登录
23 function login(data: any) {
24 data.username = data.logonUser;
25 data.password = autoSalt(data.password, false, false);
26 delete data.userType;
27 delete data.platformGuid;
28 delete data.logonUser;
29 data.needToastErr = 0;
30 data.telAreaCode = '+86';
31 isLoginOut.value = false;
32 return idaasLogin(data).then((res: any) => {
33 if (res?.code == '00000') {
34 // ElMessage.success('登录成功');
35 idaasUserInfo.value = res.data.data
36 return res.data;
37 } else {
38 ElMessage.error(res.msg)
39 }
40 })
41 }
42 return {
43 idaasToken,
44 idaasUserInfo,
45 login
46 }
47 },
48 {
49 persist:{
50 storage: localStorage,
51 paths: ['idaasToken','idaasUserInfo']
52 }
53 }
54 )
55
56 export default useIdaas
1
2
3 import { defineStore } from 'pinia'
4
5 const useLogin = defineStore(
6 // 唯一ID
7 'login',
8 () => {
9 const isCheckSms = ref(false)
10 const smsValidateCode = ref('');
11 const firstUnmetRequirement = ref('');
12 const encodePwd = ref('')
13 return {
14 /**
15 * 密文
16 */
17 encodePwd,
18 /**
19 * 是否直接去校验验证码
20 */
21 isCheckSms,
22 /**
23 * 网关登录验证码
24 */
25 smsValidateCode,
26 /**
27 * 注册密码未满足的第一条规则的 label
28 */
29 firstUnmetRequirement,
30 }
31 },
32 {
33 persist:{
34 storage: sessionStorage,
35 paths: ['firstUnmetRequirement','smsValidateCode','encodePwd']
36 }
37 }
38 )
39
40 export default useLogin
...\ No newline at end of file ...\ No newline at end of file
1 const sysConfigStore = defineStore(
2 // 唯一ID
3 'config',
4 () => {
5 let configMap: any = {};
6
7 // 封装请求配置文件的函数
8 const loadConfig = async () => {
9 try {
10 const response = await fetch('/config.json');
11 if (!response.ok) {
12 throw new Error(`请求配置失败,状态码: ${response.status}`);
13 }
14 const config = await response.json();
15 return config;
16 } catch (error) {
17 console.error('加载配置时出错:', error);
18 throw error;
19 }
20 };
21 const setConfig = (val) => {
22 configMap = val
23 }
24
25 const getConfig = (field) => {
26 if (import.meta.env.MODE == 'nginx' || import.meta.env.MODE == 'development') {
27 return import.meta.env.VITE_appKey
28 }
29 return field ? configMap[field] : configMap;
30 }
31
32 return {
33 configMap,
34 loadConfig,
35 setConfig,
36 getConfig
37 }
38 },
39 )
40
41 export default sysConfigStore
...@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' { ...@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
21 Dialog_form: typeof import('./../components/Dialog/dialog_form.vue')['default'] 21 Dialog_form: typeof import('./../components/Dialog/dialog_form.vue')['default']
22 Dialog_grid: typeof import('./../components/Dialog/dialog_grid.vue')['default'] 22 Dialog_grid: typeof import('./../components/Dialog/dialog_grid.vue')['default']
23 Dialog_pane: typeof import('./../components/Dialog/dialog_pane.vue')['default'] 23 Dialog_pane: typeof import('./../components/Dialog/dialog_pane.vue')['default']
24 DialogPlus: typeof import('./../components/DialogPlus/src/DialogPlus.vue')['default']
24 Drawer: typeof import('./../components/Drawer/index.vue')['default'] 25 Drawer: typeof import('./../components/Drawer/index.vue')['default']
25 EchartsMap: typeof import('./../components/EchartsMap/index.vue')['default'] 26 EchartsMap: typeof import('./../components/EchartsMap/index.vue')['default']
26 Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] 27 Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
...@@ -28,13 +29,17 @@ declare module '@vue/runtime-core' { ...@@ -28,13 +29,17 @@ declare module '@vue/runtime-core' {
28 FileUpload: typeof import('./../components/FileUpload/index.vue')['default'] 29 FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
29 FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default'] 30 FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default']
30 Form: typeof import('./../components/Form/index.vue')['default'] 31 Form: typeof import('./../components/Form/index.vue')['default']
32 FormItem: typeof import('./../components/FormItem/FormItem.vue')['default']
33 FormPlus: typeof import('./../components/FormPlus/src/FormPlus.vue')['default']
31 GraphTopbar: typeof import('./../components/RelationNetwork/graphTopbar.vue')['default'] 34 GraphTopbar: typeof import('./../components/RelationNetwork/graphTopbar.vue')['default']
35 Header: typeof import('./../components/Header/index.vue')['default']
32 Hour: typeof import('./../components/Schedule/component/hour.vue')['default'] 36 Hour: typeof import('./../components/Schedule/component/hour.vue')['default']
33 ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default'] 37 ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
34 ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default'] 38 ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default']
35 ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default'] 39 ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
36 LineageGraph: typeof import('./../components/LineageGraph/index.vue')['default'] 40 LineageGraph: typeof import('./../components/LineageGraph/index.vue')['default']
37 ListPanel: typeof import('./../components/ListPanel/index.vue')['default'] 41 ListPanel: typeof import('./../components/ListPanel/index.vue')['default']
42 Logo: typeof import('./../components/Logo/index.vue')['default']
38 LookBpmn: typeof import('./../components/ApprovalProcess/src/components/LookBpmn.vue')['default'] 43 LookBpmn: typeof import('./../components/ApprovalProcess/src/components/LookBpmn.vue')['default']
39 Month: typeof import('./../components/Schedule/component/month.vue')['default'] 44 Month: typeof import('./../components/Schedule/component/month.vue')['default']
40 NotAllowed: typeof import('./../components/NotAllowed/index.vue')['default'] 45 NotAllowed: typeof import('./../components/NotAllowed/index.vue')['default']
...@@ -46,6 +51,7 @@ declare module '@vue/runtime-core' { ...@@ -46,6 +51,7 @@ declare module '@vue/runtime-core' {
46 PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default'] 51 PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default']
47 Popover: typeof import('./../components/Popover/index.vue')['default'] 52 Popover: typeof import('./../components/Popover/index.vue')['default']
48 RelationNetwork: typeof import('./../components/RelationNetwork/index.vue')['default'] 53 RelationNetwork: typeof import('./../components/RelationNetwork/index.vue')['default']
54 Retrievepassword: typeof import('./../components/Retrievepassword/index.vue')['default']
49 RouterLink: typeof import('vue-router')['RouterLink'] 55 RouterLink: typeof import('vue-router')['RouterLink']
50 RouterView: typeof import('vue-router')['RouterView'] 56 RouterView: typeof import('vue-router')['RouterView']
51 Schedule: typeof import('./../components/Schedule/index.vue')['default'] 57 Schedule: typeof import('./../components/Schedule/index.vue')['default']
......
...@@ -7,6 +7,11 @@ type RecursivePartial<T> = { ...@@ -7,6 +7,11 @@ type RecursivePartial<T> = {
7 [P in keyof T]?: RecursivePartial<T[P]> 7 [P in keyof T]?: RecursivePartial<T[P]>
8 } 8 }
9 9
10 declare type ComponentRef<T> = InstanceType<T>
11
12
13 declare type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T>
14
10 declare namespace Settings { 15 declare namespace Settings {
11 interface app { 16 interface app {
12 /** 17 /**
......
...@@ -1254,4 +1254,72 @@ export const isAllPropertiesEmpty = (obj,keyList?: string[]) => { ...@@ -1254,4 +1254,72 @@ export const isAllPropertiesEmpty = (obj,keyList?: string[]) => {
1254 } 1254 }
1255 // 如果所有属性都有值,则返回true 1255 // 如果所有属性都有值,则返回true
1256 return false; 1256 return false;
1257 }
...\ No newline at end of file ...\ No newline at end of file
1257 }
1258
1259 /** 生成state的hash值 */
1260 export const createStateHashCode = () => {
1261 const array = new Uint32Array(1);
1262 window.crypto.getRandomValues(array);
1263 return array[0].toString(16) + Date.now();
1264 }
1265
1266 /**
1267 * 获取本地图
1268 * @param name // 文件名 如 doc.png
1269 * @returns {*|string}
1270 */
1271 export const getAssetsImages = (name) => {
1272 return new URL(`../assets/images/${name}`, import.meta.url).href;
1273 }
1274
1275 export const blobToImageLink = (data):Promise<string> => {
1276 return new Promise((resolve, reject) => {
1277 let blob = new Blob([data],{type:'image/png'}); // #识别文件类型
1278 let objectUrl = URL.createObjectURL(blob);
1279 resolve(objectUrl)
1280 })
1281 }
1282
1283 export function isJsonString(str) {
1284 if (typeof str !== 'string') return false;
1285 // 忽略前导和尾随空白字符
1286 str = str.trim();
1287 // 检查字符串是否以 { 或 [ 开始,并以 } 或 ] 结束
1288 if (!str.startsWith('{') && !str.startsWith('[')) return false;
1289 if (!str.endsWith('}') && !str.endsWith(']')) return false;
1290
1291 try {
1292 JSON.parse(str);
1293 return true;
1294 } catch (error) {
1295 return false;
1296 }
1297 }
1298
1299 const toString = Object.prototype.toString
1300
1301 export const is = (val: unknown, type: string) => {
1302 return toString.call(val) === `[object ${type}]`
1303 }
1304
1305 export const isEmptyVal = (val: any): boolean => {
1306 return val === '' || val === null || val === undefined
1307 }
1308
1309 export const isObject = (val: any): val is Record<any, any> => {
1310 return val !== null && is(val, 'Object')
1311 }
1312
1313 /**
1314 * 首字母大写
1315 */
1316 export function firstUpperCase(str: string) {
1317 return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
1318 }
1319
1320 /**
1321 * 驼峰转横杠
1322 */
1323 export const humpToDash = (str: string): string => {
1324 return str.replace(/([A-Z])/g, '-$1').toLowerCase()
1325 }
......
1 import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
2 import { CSSProperties } from 'vue'
3
4 type PropTypes = VueTypesInterface & {
5 readonly style: VueTypeValidableDef<CSSProperties>
6 }
7 const newPropTypes = createTypes({
8 func: undefined,
9 bool: undefined,
10 string: undefined,
11 number: undefined,
12 object: undefined,
13 integer: undefined
14 }) as PropTypes
15
16 class propTypes extends newPropTypes {
17 static get style() {
18 return toValidableType('style', {
19 type: [String, Object]
20 })
21 }
22 }
23
24 export { propTypes }
1 import axios, {CancelTokenSource} from "axios"; 1 import axios, {CancelTokenSource} from "axios";
2 import Storage from './composables/storage-helper';
3 import CryptoHelper from './composables/cryptoJs-helper'; 2 import CryptoHelper from './composables/cryptoJs-helper';
4 import { ElMessage } from 'element-plus' 3 import { ElMessage } from 'element-plus'
5 import useUserStore from '@/store/modules/user' 4 import useUserStore from '@/store/modules/user'
...@@ -13,9 +12,10 @@ interface Request { ...@@ -13,9 +12,10 @@ interface Request {
13 source: CancelTokenSource; 12 source: CancelTokenSource;
14 } 13 }
15 const pendingRequests: Request[] = []; 14 const pendingRequests: Request[] = [];
16 const storage = new Storage();
17 const cryptoHelper = new CryptoHelper('cacheKey'); 15 const cryptoHelper = new CryptoHelper('cacheKey');
18 16
17 const IDaaSBaseURL = 'idaas'
18
19 const service = axios.create({ 19 const service = axios.create({
20 baseURL: (import.meta.env.VITE_OPEN_PROXY === 'true') ? `/api/` : `${import.meta.env.VITE_API_BASEURL}`, 20 baseURL: (import.meta.env.VITE_OPEN_PROXY === 'true') ? `/api/` : `${import.meta.env.VITE_API_BASEURL}`,
21 timeout: 10 * 60 * 1000,// request timeout 21 timeout: 10 * 60 * 1000,// request timeout
...@@ -52,9 +52,15 @@ service.interceptors.request.use( ...@@ -52,9 +52,15 @@ service.interceptors.request.use(
52 config.headers['real-ip'] = localStorage.getItem('ipAddress'); 52 config.headers['real-ip'] = localStorage.getItem('ipAddress');
53 return config; 53 return config;
54 } 54 }
55 if (config.method === "idaasPost" || config.method === "idaaspost") {
56 config.baseURL = IDaaSBaseURL;
57 config.method = "post";
58 } else if (config.method === "idaasGet" || config.method === "idaasget") {
59 config.baseURL = IDaaSBaseURL;
60 config.method = "get";
61 }
55 if (config.responseType == "blob") { 62 if (config.responseType == "blob") {
56 // 文件流,文件名称相同时会判定同一个请求。 63 // 文件流,文件名称相同时会判定同一个请求。
57 const userStore = useUserStore();
58 config.headers.Authorization = localStorage.getItem('token'); 64 config.headers.Authorization = localStorage.getItem('token');
59 config.headers['real-ip'] = localStorage.getItem('ipAddress'); 65 config.headers['real-ip'] = localStorage.getItem('ipAddress');
60 return config; 66 return config;
......
1 <script lang="ts" setup name="Header">
2 import Logo from '../Logo/index.vue';
3 import useSettingsStore from '@/store/modules/settings';
4
5
6 const { proxy } = getCurrentInstance() as any;
7
8 const settingsStore = useSettingsStore();
9
10
11
12 const currentPath = computed(() => proxy.$route.path)
13
14
15
16 onMounted(()=>{
17 })
18 </script>
19
20 <template>
21 <transition name="header">
22 <header v-if="settingsStore.mode === 'pc' && settingsStore.settings.menu.menuMode === 'head'">
23 <div class="header-container">
24 <Logo />
25 <!-- <Tools v-if="currentPath !== '/portalLogin'" /> -->
26 </div>
27 </header>
28 </transition>
29 </template>
30
31 <style lang="scss" scoped>
32 header {
33 // position: fixed;
34 // z-index: 1000;
35 top: 0;
36 left: 0;
37 right: 0;
38 display: flex;
39 align-items: center;
40 height: 64px;
41 color: var(--g-header-color);
42 // background: url('@/assets/images/header_bg.png') center/100% 100% no-repeat;
43 background-color: #fff;
44 transition: background-color 0.3s, var(--el-transition-color);
45 background-size: cover;
46 // position: relative;
47
48 .header-container {
49 width: var(--g-header-width);
50 height: 100%;
51 margin: 0 auto;
52 display: flex;
53 align-items: center;
54 justify-content: space-between;
55 position: relative;
56 }
57
58 .sidebar-collapse {
59 position: absolute;
60 left: calc(var(--g-sub-sidebar-width-portal) + 10px);
61 top: 50%;
62 transform: translateY(-50%);
63 z-index: 2;
64 cursor: pointer;
65
66 :deep(.el-icon) {
67 color: #fff;
68 }
69 }
70
71 .main {
72 width: calc(var(--g-header-width) - var(--g-sub-sidebar-width-portal) - 48px);
73 height: 100%;
74 padding-right: 24px;
75 }
76
77 @media screen and (max-width: var(--g-header-width)) {
78 .header-container {
79 width: 100%;
80 }
81 }
82
83 :deep(.title) {
84 position: relative;
85 width: calc(var(--g-sub-sidebar-width-portal) + 18px);
86 height: inherit;
87 padding-left: 18px;
88 padding-right: 0;
89 background-color: inherit;
90 flex-shrink: 0;
91
92 span {
93 font-size: 24px;
94 letter-spacing: 1px;
95 color: var(--g-header-color);
96 }
97 }
98
99 .nav {
100 display: flex;
101 width: 100%;
102 height: 100%;
103 padding: 0 25px;
104 align-items: center;
105 overflow-x: auto;
106 mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
107
108 // firefox隐藏滚动条
109 scrollbar-width: none;
110
111 // chrome隐藏滚动条
112 &::-webkit-scrollbar {
113 display: none;
114 }
115
116 .item-container {
117 position: relative;
118 display: flex;
119 width: initial;
120 height: inherit;
121
122 .item {
123 padding: 0 12px;
124 display: flex;
125 align-items: center;
126 justify-content: center;
127 flex-direction: column;
128 min-width: 60px;
129 height: 100%;
130 cursor: pointer;
131 color: var(--g-header-menu-color);
132 background-color: transparent;
133 // transition: background-color 0.3s, var(--el-transition-color);
134
135 &:hover {
136 color: var(--g-header-menu-hover-color);
137 background-color: var(--g-header-menu-hover-bg);
138 }
139
140 .el-icon {
141 font-size: 24px;
142 vertical-align: middle;
143 }
144
145 span {
146 text-align: center;
147 vertical-align: middle;
148 word-break: break-all;
149
150 @include text-overflow(1, false);
151 }
152 }
153
154 &.active .item {
155 color: var(--g-header-menu-active-color);
156 background-color: var(--g-header-menu-active-bg);
157 }
158 }
159 }
160
161 :deep(.tools) {
162 .buttons .item .el-icon {
163 color: var(--g-header-color);
164 }
165
166 .user-container {
167 font-size: 16px;
168 color: var(--g-header-color);
169 outline: none !important;
170 }
171 }
172 }
173
174 // 头部动画
175 .header-enter-active,
176 .header-leave-active {
177 transition: transform 0.3s;
178 }
179
180 .header-enter-from,
181 .header-leave-to {
182 transform: translateY(calc(var(--g-header-height-portal) * -1));
183 }
184 </style>
1 <script lang="ts" setup name="Logo">
2 import imgLogo from '@/assets/images/数据资产运营平台logo.png'
3 import useSettingsStore from '@/store/modules/settings'
4
5 defineProps({
6 showLogo: {
7 type: Boolean,
8 default: true,
9 },
10 showTitle: {
11 type: Boolean,
12 default: true,
13 },
14 })
15
16 const settingsStore = useSettingsStore()
17
18 const title = ref(import.meta.env.VITE_APP_TITLE)
19 const logo = ref(imgLogo)
20
21 const to = computed(() => {
22 const rtn: {
23 name?: string
24 } = {}
25 if (settingsStore.settings.home.enable) {
26 rtn.name = 'portal'
27 }
28 return rtn
29 })
30 </script>
31
32 <template>
33 <div class="title">
34 <img v-if="showLogo" :src="logo" class="logo">
35 <!-- <router-link :to="to" :class="{ 'is-link': settingsStore.settings.home.enable }" :title="title">
36 <img v-if="showLogo" :src="logo" class="logo">
37 </router-link>
38 <el-divider direction="vertical" /> -->
39 </div>
40
41 </template>
42
43 <style lang="scss" scoped>
44 .title {
45 position: fixed;
46 z-index: 1000;
47 top: 0;
48 width: inherit;
49 padding: 0 10px;
50 display: flex;
51 align-items: center;
52 justify-content: space-between;
53 height: var(--g-sidebar-logo-height);
54 text-align: center;
55 overflow: hidden;
56 text-decoration: none;
57
58 &.is-link {
59 cursor: pointer;
60 }
61
62 .logo {
63 width: 264px;
64 height: 64px;
65 object-fit: contain;
66
67 &+span {
68 margin-left: 10px;
69 }
70 }
71
72 span {
73 display: block;
74 font-weight: bold;
75 color: #fff;
76
77 @include text-overflow;
78 }
79
80 .el-divider {
81 height: 1.5rem;
82 margin-right: 0;
83 }
84
85 }
86 </style>
1 <template>
2 <div class="tools">
3 <div class="user-container" @click="enterUrl">
4 <div class="color-21 mark-icon">
5 <img :src="imgIcon" alt="" />
6 <span>{{ '知识库' }}</span>
7 </div>
8 </div>
9 <el-popover :width="280" :show-arrow="false" popper-class="user-setting">
10 <template #reference>
11 <div class="user-container">
12 <div class="user-wrapper">
13 <span class="max-w-120px p-r-5px">
14 <div class="nowrap-ellipsis text-right color-21"
15 style="color: #212121;font-weight: 600; line-height: 1.5;">
16 {{ userStore.userName || userData.staffName }}
17 </div>
18 <div class="nowrap-ellipsis text-right color-21" style="color: #666; font-size: 12px; line-height: 1.5;">
19 {{ userData.abbreviation }}
20 </div>
21 </span>
22 <el-avatar size="default">
23 <el-icon size="20px">
24 <svg-icon name="ep:user-filled" />
25 </el-icon>
26 </el-avatar>
27 </div>
28 </div>
29 </template>
30 <template #default>
31 <div class="avatar-info">
32 <div class='flex justify-start align-mid flex-items-center'>
33 <el-avatar :src="getAssetsImages('avatar.png')">
34 </el-avatar>
35 <div class="p-r-5px p-l-8px">
36 <div class="text-left font-600 font-size-18px color-21">
37 {{ userStore.userName || userData.staffName }}
38 </div>
39 <div class="text-left">
40 账号: {{ userData.mobileNo.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
41 </div>
42 </div>
43 </div>
44 </div>
45 <div class="horizontal-line"></div>
46 <!-- <div class="user-setting-menu">
47 <div class="menu-wrapper" v-show="isNonEmptyArray(tenantInfoList) && tenantInfoList.length > 1"
48 @click="userCommand('changeTenant')">
49 <span class="color-21">切换公司</span>
50 <span>
51 <Icon icon="ep:arrow-right" :size='16' />
52 </span>
53 </div>
54 </div> -->
55 <!-- <div class="horizontal-line" v-show="isNonEmptyArray(tenantInfoList) && tenantInfoList.length > 1"></div> -->
56 <div class="user-setting-menu">
57 <div class="menu-wrapper" @click="userCommand('updatePwd')">
58 <span class="color-21">修改密码</span>
59 <span>
60 <Icon icon="ep:arrow-right" :size='16' />
61 </span>
62 </div>
63 </div>
64 <div class="horizontal-line"></div>
65 <div class="user-setting-menu">
66 <div class="menu-wrapper" @click="userCommand('stopUser')">
67 <span class="color-21">注销登录</span>
68 <span>
69 <Icon icon="ep:arrow-right" :size='16' />
70 </span>
71 </div>
72 </div>
73 <template v-if="bindList && bindList.length">
74 <div class="horizontal-line"></div>
75 <div class="user-setting-menu">
76 <div class="menu-wrapper" @click="userCommand('passKeyUnbind')">
77 <span class="color-21">通行密钥解绑</span>
78 <span>
79 <Icon icon="ep:arrow-right" :size='16' />
80 </span>
81 </div>
82 </div>
83 </template>
84 <div class="horizontal-line"></div>
85 <div class="login-out">
86 <div class="login-out-btn" @click="userCommand('logout')">
87 {{ '退出登录' }}
88 </div>
89 </div>
90 </template>
91 </el-popover>
92 <Retrievepassword :schemaInfo="retrievepassword"></Retrievepassword>
93 <!-- <ChangeTenant :schemaInfo="schemaInfo" @confirm="selectSure" @close="() => { schemaInfo.visible = false; }">
94 </ChangeTenant> -->
95 </div>
96 </template>
97
98 <script lang="ts" setup name="Tools">
99 import { ElMessageBox } from 'element-plus';
100 import Retrievepassword from '../Retrievepassword/index.vue'
101 import useUserStore from '@/store/modules/user'
102 import imgIcon from '@/assets/images/知识库.png'
103 import { getAssetsImages, isNonEmptyArray } from '@/utils/common';
104
105 const userStore = useUserStore();
106 const userData = JSON.parse(userStore.userData)
107
108 const { proxy } = getCurrentInstance() as any;
109 const router = useRouter()
110
111 // ! 修改密码逻辑
112 const retrievepassword = ref({
113 visible: false,
114 })
115
116 const bindList = ref([])
117
118 const enterUrl = () => {
119 window.open('https://data-assets.zgene.cn/1zcdj');
120 }
121
122 function userCommand(command: 'logout' | 'updatePwd' | 'passKeyUnbind' | 'stopUser') {
123 switch (command) {
124 case 'updatePwd':
125 retrievepassword.value.visible = true
126 break
127 case 'logout':
128 logout()
129 break
130 case 'stopUser':
131 stopLogonUser()
132 break
133 case 'passKeyUnbind':
134 ElMessageBox.confirm('确定解绑吗, 是否继续?', "提示", {
135 confirmButtonText: "确定",
136 cancelButtonText: "取消",
137 type: 'warning',
138 }).then(() => {
139 // TODO
140 // let list: any = [];
141 // bindList.value.forEach(item => {
142 // list.push(item.guid)
143 // })
144 // webAuth.unbindWebAuths(list).then((respones: any) => {
145 // console.log(respones)
146 // if (respones.data.code == '00000') {
147 // bindList.value = [];
148 // ElMessage({
149 // type: "success",
150 // message: "解绑成功!",
151 // });
152 // }
153 // })
154 })
155 }
156 }
157
158 function logout() {
159 // proxy.$openMessageBox("是否确认退出登录", "warning", () => {
160 // userApi.logout().then(() => {
161 // userStore.logout({
162 // logMessage: 'logout-常触发退出登录'
163 // })
164 // localStorage.removeItem("token");
165 // localStorage.removeItem("refreshToken");
166 // localStorage.removeItem("tokenTimestamp");
167 // localStorage.removeItem("user");
168 // proxy.$router.push('/portalLogin')
169 // })
170 // })
171 }
172
173 /** 注销账号 */
174 function stopLogonUser() {
175 // proxy.$openMessageBox(userData?.isAdmin === 'Y' ? "是否确认注销当前管理员用户及其所在企业下所有的用户?" : "是否确认注销当前用户?", "warning", () => {
176 // staffInfoApi.stopLogonUser(userData?.staffGuid).then((res) => {
177 // if (res.data.code == '00000') {
178 // ElMessage({
179 // type: "success",
180 // message: "注销账号成功",
181 // });
182 // userApi.logout().then(() => {
183 // userStore.logout({
184 // logMessage: 'logout-常触发退出登录'
185 // })
186 // localStorage.removeItem("token");
187 // localStorage.removeItem("refreshToken");
188 // localStorage.removeItem("tokenTimestamp");
189 // localStorage.removeItem("user");
190 // proxy.$router.push('/portalLogin')
191 // })
192 // } else {
193 // ElMessage({
194 // type: "error",
195 // message: "注销账号失败",
196 // });
197 // }
198 // });
199 // })
200 }
201
202 onMounted(() => {
203 // webAuth.getCurrentUserWebAuths().then((res: any) => {
204 // let list = res.data;
205 // if (list && list.length) {
206 // bindList.value = list;
207 // }
208 // })
209 });
210 </script>
211
212
213
214 <style lang="scss" scoped>
215 :deep(.el-popper) {
216 padding: 0;
217 }
218
219 :deep(.user-setting) {
220 padding: 0 !important;
221 }
222
223 :deep(.el-radio-group) {
224 .el-radio--large {
225 width: 420px;
226 }
227 }
228
229 .tools {
230 display: flex;
231 align-items: center;
232 white-space: nowrap;
233
234
235 .buttons {
236 .item {
237 display: inline-flex;
238 align-items: center;
239 justify-content: center;
240 height: 24px;
241 width: 34px;
242 cursor: pointer;
243 vertical-align: middle;
244
245 .el-icon {
246 color: var(--el-text-color-primary);
247 transition: var(--el-transition-color);
248 }
249 }
250
251 .item-pro {
252 display: inline-flex;
253 align-items: center;
254 width: auto;
255 padding: 0 10px;
256 transform-origin: right center;
257 animation: pro-text 3s ease-out infinite;
258
259 @keyframes pro-text {
260
261 0%,
262 20% {
263 transform: scale(1);
264 }
265
266 50%,
267 70% {
268 transform: scale(1.2);
269 }
270
271 100% {
272 transform: scale(1);
273 }
274 }
275
276 .title {
277 padding-left: 5px;
278 font-weight: bold;
279 font-size: 14px;
280 background-image: linear-gradient(to right, #ffa237, #fc455d);
281 background-clip: text;
282 -webkit-text-fill-color: transparent;
283 }
284 }
285 }
286 }
287
288 .avatar-info {
289 padding-bottom: 10px;
290 }
291
292 :deep(.user-container) {
293 display: inline-block;
294 cursor: pointer;
295 height: var(--g-header-height-portal);
296 // line-height: var(--g-header-height-portal);
297 display: flex;
298 align-items: center;
299 padding: 0;
300
301 .user-wrapper {
302 display: flex;
303 align-items: center;
304 padding-left: 12px;
305 padding-right: 24px;
306
307 .el-avatar {
308 vertical-align: middle;
309 margin-top: -2px;
310 }
311 }
312 }
313
314 .user-container:hover {
315 background: #f6f6f6;
316 }
317
318 .segmentation {
319 display: flex;
320 justify-content: center;
321 /* 水平居中 */
322 align-items: center;
323 /* 垂直居中 */
324 height: var(--g-header-height-portal);
325 /* 容器的高度为视口高度 */
326 }
327
328 .vertical-line {
329 width: 1px;
330 /* 分割线的宽度 */
331 height: 70%;
332 /* 分割线的高度 */
333 background-color: #fff;
334 /* 白色背景 */
335 }
336
337 .horizontal-line {
338 border: none;
339 height: 1px;
340 background-color: #E5E5E5;
341 width: 100%;
342 /* 宽度为容器的100% */
343 }
344
345 .menu-wrapper {
346 cursor: pointer;
347 display: flex;
348 justify-content: space-between;
349 padding: 10px 0;
350 }
351
352 .menu-wrapper:hover {
353 background: #F5F5F5;
354 }
355
356 .login-out {
357 cursor: pointer;
358 text-align: center;
359 padding: 0 10px;
360 }
361
362 .login-out-btn {
363 height: 32px;
364 line-height: 32px;
365 margin-top: 10px;
366 width: 100%;
367 background: #FFFFFF;
368 border: 1px solid rgba(217, 217, 217, 1);
369 }
370
371 .color-21 {
372 color: #212121;
373 font-size: 14px;
374 padding-right: 12px;
375
376 &.mark-icon {
377 padding: 0 16px;
378 display: flex;
379 align-items: center;
380 position: relative;
381
382 img {
383 width: 24px;
384 height: 24px;
385 margin-right: 8px;
386 }
387
388 &::after {
389 height: 40px;
390 content: '';
391 border-right: 1px solid #E5E5E5;
392 position: absolute;
393 top: 50%;
394 right: 0;
395 transform: translateY(-50%);
396 }
397 }
398 }
399
400 :deep(.el-input.captcha) {
401 .el-input__wrapper {
402 border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
403 }
404
405 .el-input-group__append {
406 width: 150px;
407 margin-left: 8px;
408 box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
409 border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
410 }
411
412 img {
413 cursor: pointer;
414 }
415 }
416 </style>
1
2 <template>
3 <div class="main-h">
4 <Header />
5 <div class="bg-banner">
6 <div id="login-box" >
7 <div :class="['login_form', (formType === 'login'||formType === 'beforeLogin'|| formType === 'loginByPassKey'||formType === 'resignByPassKey') ? 'login-size' : 'register-size']" :style="formType === 'loginByPassKey'?'height:280px;z-index: 1001':'z-index: 1001'">
8 <!-- <img v-if="formType !== 'loginByPassKey'&&formType !== 'resignByPassKey'&&formType !== 'login'" :src="imgSrc" @click="switchHandle" class="login-img"> -->
9 <div class="login-title" :style="{ marginBottom: loginTitle == '通行密钥登录' ? '30px' : '0px' }">{{ loginTitle }}</div>
10 <!-- 登录密码验证 -->
11 <FormItem class="pt-30px" v-if="formType === 'beforeLogin'||formType==='resignByPassKey'" ref="beforeLoginFormRef" :schemaParam="beforeLoginSchema" />
12 <!-- 正式登录 -->
13 <FormItem class="pt-30px" v-if="formType === 'login'" ref="loginFormRef" :schemaParam="loginSchema" />
14 <!-- 注册表单 -->
15 <FormItem class="pt-30px" v-if="formType === 'register'" ref="registerForm"
16 :schemaParam="registerSchema" />
17 <div class="xieyi">
18 <el-checkbox v-model="isAgree">我已阅读并同意<span style="color: #4fa1a4"
19 @click.stop.prevent="newOpen(0)">《隐私声明》</span><span style="color: #4fa1a4"
20 @click.stop.prevent="newOpen(1)">《用户协议》</span></el-checkbox>
21 </div>
22 <!-- before登录 -->
23 <div v-if="formType === 'beforeLogin'" class="login-footer">
24 <el-button class="loginButton" :loading="loading" :disabled="!isAgree" ref="loginButton" type="primary" size="large" style="width: 100%;" @click.prevent="beforeLogin">
25 登录
26 </el-button>
27 <div class="flex" style="justify-content: space-between;">
28 <el-button class="forget-pwd-btn" link size="small" @click.prevent="formType='loginByPassKey'">
29 通行密钥登录
30 </el-button>
31 <el-button class="forget-pwd-btn" link size="small" @click.prevent="retrievePassword">
32 忘记密码
33 </el-button>
34 </div>
35
36 </div>
37 <!-- 继续登录 -->
38 <div v-if="formType === 'login'" class="login-footer">
39 <el-button class="loginButton" :loading="loading" ref="loginButton" type="primary" size="large" style="width: 100%;" @click.prevent="verifyUser">
40 继续登录
41 </el-button>
42 <div class="flex" style="justify-content: space-between;">
43 <el-button class="forget-pwd-btn" link size="small" @click.prevent="formType='loginByPassKey'">
44 通行密钥登录
45 </el-button>
46 <el-button class="forget-pwd-btn" link size="small" @click.prevent="retrievePassword">
47 忘记密码
48 </el-button>
49 </div>
50
51 </div>
52 <!--通行密钥登录 -->
53 <div v-if="formType === 'loginByPassKey'" class="login-footer">
54 <el-button class="loginButton" :loading="passKeyLoginloading" ref="loginButton" :disabled="!isAgree" type="primary" size="large" style="width: 100%;" @click.prevent="passKeyLogin">
55 登录
56 </el-button>
57 <div class="flex" style="justify-content: space-between;">
58 <el-button class="forget-pwd-btn" link size="small" @click.prevent="formType='beforeLogin'">
59 账号密码登录
60 </el-button>
61 <el-button class="forget-pwd-btn" link size="small" @click.prevent="formType='resignByPassKey'">
62 去注册
63 </el-button>
64 </div>
65 </div>
66 <!-- 通行密钥登录 -->
67
68 <!--通行密钥注册 -->
69 <div v-if="formType === 'resignByPassKey'" class="login-footer" >
70 <el-button class="loginButton" :loading="passKeyRegisterloading" ref="loginButton" type="primary" size="large" style="width: 100%;" @click.prevent="passKeySign">
71 注册
72 </el-button>
73 <el-button class="forget-pwd-btn" link size="small" @click.prevent="formType='loginByPassKey'">
74 去登录
75 </el-button>
76 </div>
77 <!-- 通行密钥注册 -->
78
79 <!-- <div v-if="formType === 'register'" class="login-footer">
80 <el-button :loading="loading" :disabled="!isAgree" type="primary" size="large" style="width: 100%;" @click.prevent="handleRegister">
81 注册
82 </el-button>
83 </div> -->
84 </div>
85 </div>
86 <div class="copyright_text">
87 <span>Copyright © 2015-2024</span>
88 <a style="color: #4FA1A4;margin: 0 8px;" href="https://beian.miit.gov.cn" target="_blank">京ICP备2024044205号</a>
89 <span>北京传世博润科技有限公司</span>
90 </div>
91 </div>
92 </div>
93 <!-- <el-dialog v-model="centerDialogVisible" width="360" title="图形验证码" align-center>
94 <template #default>
95 <div class="img-code">
96 <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="demo-ruleForm" @submit.native.prevent>
97 <el-form-item>
98 <div class="img_code">
99 <img :src="imgCodePath" />
100 <span>看不清?<span class="text_btn" @click="getImgCode">换一张</span></span>
101 </div>
102 </el-form-item>
103 <el-form-item prop="captcha">
104 <el-input v-model="ruleForm.captcha" placeholder="请输入图形验证码" clearable @keyup.enter="sendCode" />
105 </el-form-item>
106 </el-form>
107 </div>
108 </template>
109 <template #footer>
110 <div class="dialog-footer">
111 <el-button type="primary" size="large" @click="sendCode">确定</el-button>
112 </div>
113 </template>
114 </el-dialog> -->
115 <AsyncRetrievepassword :schemaInfo="retrievepassword" :operate="operate"></AsyncRetrievepassword>>
116 <!-- 图形验证码 -->
117 <!-- <DialogPlus modal-class="auth-user" append-to-body v-model="imgCheckDialog" width="460px"
118 maxHeight="150px" :close-on-click-modal="false" title="图形验证">
119 <div class="select-tenant">
120 <el-form label-position="top" ref="pictureFormRef" :model="pictureFormData"
121 :rules="pictureRules" class="login-form" auto-complete="on">
122 <el-form-item prop="validateCode" label="图形验证码">
123 <el-input class="captcha" v-model.trim="pictureFormData.validateCode" placeholder="请输入图形验证码" tabindex="2"
124 autocomplete="on">
125 <template #append>
126 <img class="h-26px" :src="imgCaptchaBase64" @click="refreshPictureCode" />
127 </template>
128 </el-input>
129 </el-form-item>
130 </el-form>
131 </div>
132 <template #footer>
133 <el-button @click="()=>{imgCheckDialog=false}">取 消</el-button>
134 <el-button :loading="sendCodeLoading" type="primary"
135 @click="checkPictureCode">发送验证码至手机</el-button>
136 </template>
137 </DialogPlus> -->
138 </template>
139
140
141 <script lang="tsx" setup name="portalLogin">
142 import { defineAsyncComponent } from 'vue';
143 import Header from './components/Header/index.vue'
144 import useUserStore from '@/store/modules/user';
145 import useIdaasStore from '@/store/modules/idaas';
146 import sysConfigStore from '@/store/modules/sysConfig'
147 import { useValidator } from '@/hooks/useValidator'
148 import { FormSchema } from '@/components/FormPlus'
149 import useCountdown from '@/hooks/useCountdown'
150 import { ElMessageBox } from 'element-plus'
151 import {
152 checkLoginUser,
153 checkDeviceTypeRegist,
154 signUp,
155 } from '@/api/modules/idaas';
156 import type { FormRules } from 'element-plus'
157 import { v4 as uuidv4 } from 'uuid';
158 import Base64url from '@/hooks/base64url'
159 import md5 from 'md5';
160 import useLogin from '@/store/modules/login'
161 import CryptoJS from 'crypto-js'
162 import { createStateHashCode, getAssetsImages, blobToImageLink, isJsonString } from '@/utils/common';
163 import {
164 getLoginWebAuthn, sendLoginCode, checkImgCode, registWebAuthn, getWebAuth4jLogin
165 } from '@/api/modules/idaas'
166
167 const AsyncPasswordStrengthMeter = defineAsyncComponent(() =>
168 import('../../components/PasswordStrengthMeter/index.vue')
169 );
170 const AsyncRetrievepassword = defineAsyncComponent(() =>
171 import('../../components/Retrievepassword/index.vue')
172 );
173 const loginStore = useLogin()
174 const { required, phone, isUSCCCode } = useValidator();
175
176 const { proxy } = getCurrentInstance() as any;
177 const route = useRoute();
178
179 const loginButton = ref()
180
181 const isAgree = ref(false);
182
183 /** 打开用户阅读协议 */
184 function newOpen(num) {
185 if (num == 0) {
186 window.open(window.location.origin + '/userPrivate')
187 } else {
188 window.open(window.location.origin + '/userAgree')
189 }
190 }
191
192 // ! 忘记密码
193 const retrievepassword = ref({
194 visible: false
195 })
196 // retrievepassword(忘记密码) modifypassword(修改密码)
197 const operate = ref('retrievepassword')
198
199 /**
200 * 去修改密码
201 * @param param0
202 */
203 function changePassword({
204 mobileNo,
205 logonUser,
206 name
207 }) {
208 idaasStore.idaasUserInfo = {
209 principal: {
210 mobileNo: mobileNo,
211 logonUser: logonUser,
212 name: name
213 }
214 }
215 // 去修改逻辑
216 retrievePwdHanlder('modifypassword')
217 }
218
219 /**
220 * 忘记密码
221 */
222 async function retrievePassword() {
223 if (logonUser.value) {
224 let res: any = await checkLoginUser(logonUser.value);
225 if (res?.code == '00000') {
226 let checkRes = res.data || {};
227 if (checkRes) {
228 changePassword({
229 mobileNo: checkRes.mobileNo,
230 logonUser: checkRes.logonUser,
231 name: checkRes.userName
232 })
233 } else {
234 retrievePwdHanlder()
235 }
236 } else {
237 res?.msg && proxy.$ElMessage.error(res.msg);
238 }
239 } else {
240 let formData = await beforeLoginFormRef.value.getData();
241 if (formData.logonUser) {
242 logonUser.value = formData.logonUser;
243 retrievePassword()
244 } else {
245 retrievePwdHanlder()
246 }
247 }
248 logonUser.value = ''
249 }
250
251 /**
252 * 找回密码处理函数
253 * @param type
254 */
255 function retrievePwdHanlder(type = 'retrievepassword') {
256 retrievepassword.value.visible = true;
257 operate.value = type
258 }
259
260 const formTypeMap = {
261 resignByPassKey: '通行密钥注册',
262 loginByPassKey: '通行密钥登录',
263 beforeLogin: '手机号登录',
264 login: '登录验证',
265 // 默认情况(比如 register 或其他)映射到 '注册申请'
266 };
267
268 const loginTitle = computed(() => {
269 return formTypeMap[formType.value] || '注册申请';
270 });
271
272 const formType = ref(proxy.$route.query.formType || 'beforeLogin');
273 const beforeLoginFormRef = ref() // 登录前表单
274 const loginFormRef = ref() // 登录表单
275 const registerForm = ref() // 注册表单
276 const pictureFormRef = ref() //
277 const pictureFormData = ref<any>({}) // 注册表单
278 const pictureRules = ref<FormRules>({
279 validateCode: [
280 { required: true, trigger: 'blur', message: '请输入图形验证码' },
281 {
282 // 图形验证码校验
283 validator: (rule: any, value: any, callback: any) => {
284 if (value && vCode.value && value !== vCode.value) {
285 callback(new Error('验证失败'));
286 } else if (value && vCode.value) {
287 callback();
288 }
289 callback();
290 },
291 trigger: "change",
292 }
293 ]
294 })
295 /** 图形验证码图片。 */
296 const imgCaptchaBase64 = ref('');
297 const vCode = ref(''); // 图片校验的编码
298 // 登录表单
299 const beforeLoginSchema = reactive<FormSchema[]>([
300 {
301 field: 'logonUser',
302 label: '请输入手机号/账号',
303 component: 'Input',
304 colProps: {
305 span: 24
306 },
307 componentProps: {},
308 formItemProps: {
309 rules: [required()], // 校验规则
310 }
311 },
312 {
313 field: 'password',
314 label: '请输入密码',
315 component: 'Input',
316 colProps: {
317 span: 24
318 },
319 componentProps: {
320 showPassword: true,
321 autocomplete: 'current-password'
322 },
323 formItemProps: {
324 rules: [required()], // 校验规则
325 }
326 },
327 ])
328 // 登录表单
329 const loginSchema = reactive<FormSchema[]>([
330 {
331 field: 'mobileNo',
332 label: '验证手机号',
333 component: 'Input',
334 colProps: {
335 span: 24
336 },
337 componentProps: {
338 disabled: true,
339 readonly: true,
340 formatter: (value: string) => {
341 if (!value) return '';
342 const str = String(value); // 确保是字符串
343 if (str.length < 7) return str; // 长度不够不处理
344 return str.substring(0, 3) + '****' + str.substring(7);
345 }
346 },
347 formItemProps: {
348 rules: [required()], // 校验规则
349 }
350 },
351 {
352 field: 'smsCode',
353 label: '短信验证码',
354 component: 'Input',
355 colProps: {
356 span: 24
357 },
358 componentProps: {
359 slots: {
360 append: () => {
361 return (
362 <>
363 {
364 sendCodeLoading2.value ?
365 <span class="w-80px text-center cursor-not-allowed fontC-4fa1a4">{`重新获取(${timeLeft2.value})s`}</span>
366 :
367 <span class="text-center cursor-pointer fontC-4fa1a4" v-show={!sendCodeLoading2.value} onClick={() => {
368 getLoginSmsCode()
369 }}>{sendCodeText.value}</span>
370 }
371 </>
372 )
373 }
374 }
375 },
376 formItemProps: {
377 rules: [
378 {
379 validator: (rule: any, value: any, callback: any) => {
380 if (disabledverifyUser.value) {
381 callback(new Error('请先获取验证码'));
382 }
383 callback()
384 },
385 trigger: ["change","blur"],
386 },
387 required(),
388 ], // 校验规则
389 }
390 },
391 ])
392 const switchHandle = () => {
393 formType.value = formType.value === 'beforeLogin' ? 'register' : 'beforeLogin';
394 clearFormData()
395 }
396
397 // 重置表单
398 function clearFormData() {
399 password.value = ''
400 beforeLoginFormRef.value.setValue({}, true);
401 registerForm.value.setValue({}, true);
402 }
403
404 /**
405 * 校验注册密码
406 */
407 function validatorPassword(rule, value, callback) {
408 if (!password.value) {
409 callback(new Error('该项为必填项'))
410 } else if (loginStore.firstUnmetRequirement) {
411 callback(new Error(`需要${loginStore.firstUnmetRequirement}`))
412 } else {
413 callback();
414 }
415 }
416
417 /**
418 * 校验确认密码
419 */
420 function validatorConfirmpwd(rule, value, callback) {
421 if (value != password.value) {
422 callback(new Error('密码不一致,请重新输入'))
423 } else {
424 callback();
425 }
426 }
427
428 // 注册表单
429 const password = ref('')
430 const registerSchema = reactive<FormSchema[]>([
431 {
432 field: 'tenantName',
433 label: '企业名称',
434 component: 'Input',
435 colProps: {
436 span: 24
437 },
438 componentProps: {
439 maxlength: 100,
440 },
441 formItemProps: {
442 rules: [required()], // 校验规则
443 }
444 },
445 {
446 field: 'tenantCode',
447 label: '统一社会信用代码',
448 component: 'Input',
449 colProps: {
450 span: 12
451 },
452 componentProps: {
453 maxlength: 200,
454 },
455 formItemProps: {
456 rules: [required(), isUSCCCode('统一社会信用代码格式不正确')], // 校验规则
457 }
458 },
459 {
460 field: 'userName',
461 label: '账号',
462 component: 'Input',
463 colProps: {
464 span: 12
465 },
466 componentProps: {
467 maxlength: 20,
468 },
469 formItemProps: {
470 rules: [required(),phone(), /* beforeRegisterCheck('name')*/], // 校验规则
471 }
472 },
473 {
474 field: 'password',
475 label: '登录密码',
476 component: 'Input',
477 colProps: {
478 span: 12
479 },
480 componentProps: {
481 maxlength: 20,
482 type: 'password',
483 showPassword: true,
484 autocomplete : 'new-password'
485 },
486 formItemProps: {
487 // 校验规则
488 rules: [
489 required(),
490 {
491 validator: validatorPassword,
492 trigger: ['change','blur']
493 },
494 ],
495 slots: {
496 default: () => {
497 return (
498 <>
499 <AsyncPasswordStrengthMeter v-model={password.value} onChange={() => {
500 registerForm.value.setValue({
501 password: password.value
502 })
503 registerForm.value.formValidation(['password'])
504 }}/>
505 </>
506 )
507 }
508 }
509 }
510 },
511 {
512 field: 'confirmpwd',
513 label: '确认登录密码',
514 component: 'Input',
515 colProps: {
516 span: 12
517 },
518 componentProps: {
519 placeholder:'请输入密码',
520 maxlength: 20,
521 type: 'password',
522 showPassword: true,
523 clearable: false,
524 autocomplete : 'new-password'
525 },
526 formItemProps: {
527 // 校验规则
528 rules: [
529 required(),
530 {
531 validator: validatorConfirmpwd,
532 trigger: ['change','blur']
533 },
534 ],
535 }
536 },
537 {
538 field: 'mobileNo',
539 label: '联系方式',
540 component: 'Input',
541 colProps: {
542 span: 24
543 },
544 componentProps: {
545 maxlength: 20,
546 },
547 formItemProps: {
548 rules: [required(), phone(), /*beforeRegisterCheck('mobileNo')*/], // 校验规则
549 }
550 },
551 {
552 field: 'smsCode',
553 label: '短信验证码',
554 component: 'Input',
555 colProps: {
556 span: 24
557 },
558 componentProps: {
559 slots: {
560 append: () => {
561 return (
562 <>
563 {
564 sendCodeLoading.value ?
565 <span class="w-80px text-center fontC-4fa1a4">{`重新获取(${timeLeft.value})s`}</span>
566 :
567 <span class="text-center cursor-pointer fontC-4fa1a4" v-show={!sendCodeLoading.value} onClick={() => {
568 // getRegisterImgCode()
569 }}>{'获取短信验证码'}</span>
570 }
571 </>
572 )
573 }
574 }
575 },
576 formItemProps: {
577 rules: [required()], // 校验规则
578 }
579 },
580 ])
581
582 const userStore = useUserStore()
583 const idaasStore = useIdaasStore();
584
585 /** 登录按钮的loading */
586 const loading = ref(false)
587 /** 发送短信验证码的loading */
588 const sendCodeLoading = ref(false);
589 const sendCodeLoading2 = ref(false);
590 const sendCodeText = ref('获取短信验证码');
591 const disabledverifyUser = ref(true)
592 /** 重置密码保存的loading */
593 const saveLoading = ref(false);
594
595 // ! 图形验证
596 const centerDialogVisible = ref(false);
597 const imgCodePath = ref('')
598 const imgCodeGuid = ref('');
599 const ruleFormRef = ref();
600 const ruleForm = ref({
601 captcha: ''
602 })
603 const rules = ref({
604 captcha: [
605 {
606 validator: (rule: any, value: any, callback: any) => {
607 if (value === '') {
608 callback(new Error('请填写图形验证码'))
609 } else {
610 let params: any = {
611 captcha: value,
612 captchaGuid: imgCodeGuid.value
613 };
614 checkImgCode(params).then((res: any) => {
615 if (res.code == proxy.$passCode) {
616 if (!res.data) {
617 callback(new Error("验证码错误,请重新填写"));
618 } else {
619 callback();
620 }
621 } else {
622 callback(new Error(res.msg));
623 }
624 }).catch((xhr) => {
625 callback(new Error(xhr.msg));
626 });
627 }
628 }, trigger: 'blur'
629 },
630 {
631 min: 1,
632 message: '请填写图形验证码',
633 trigger: 'change',
634 },
635 ]
636 })
637
638 // const getImgCode = () => {
639 // getImgCodeSrc({ width: 180, height: 40 }).then((res) => {
640 // imgCodeGuid.value = res.data.data?.guid || '';
641 // imgCodePath.value = res.data.data?.imageBase64 || '';
642 // if (!centerDialogVisible.value) {
643 // ruleForm.value.captcha = '';
644 // centerDialogVisible.value = true;
645 // nextTick(() => {
646 // ruleFormRef.value.clearValidate();
647 // })
648 // }
649 // })
650 // }
651
652 const { timeLeft, minutes, seconds, start, stop, reset } = useCountdown(60);
653
654 watchEffect(() => {
655 if (timeLeft.value == 0) {
656 reset()
657 sendCodeLoading.value = false
658 }
659 })
660
661
662 // 身份验证短信获取倒计时
663 const { timeLeft:timeLeft2, start:start2, reset:reset2 } = useCountdown(60);
664 watchEffect(() => {
665 if (timeLeft2.value == 0) {
666 reset2()
667 sendCodeText.value = '再次发送'
668 sendCodeLoading2.value = false
669 }
670 })
671 /**
672 * 手机号身份验证
673 */
674 async function getLoginSmsCode() {
675 let validate = await loginFormRef.value.formValidation(['mobileNo']);
676 if (validate) {
677 let params = await loginFormRef.value.getData()
678 sendLoginCode(params.mobileNo).then(res=>{
679 if (res.data.code == proxy.$passCode) {
680 sendCodeLoading2.value = true;
681 disabledverifyUser.value = false;
682 loginFormRef.value.formValidation(['smsCode']);
683 start2()
684 }
685 });
686 }
687 }
688
689 // // 获取注册的图片验证
690 // const getRegisterImgCode = async () => {
691 // let validate = await registerForm.value.formValidation(['mobileNo'])
692 // if (validate) {
693 // imgCheckDialog.value = true;
694 // pictureFormData.value.validateCode = null;
695 // await nextTick();
696 // getCommoncheckImgCode()
697 // }
698 // }
699
700 // function getCommoncheckImgCode() {
701 // commoncheckImgCode().then(async (res) => {
702 // if (res.status == 200) {
703 // vCode.value = res.headers['v-code'] || ''
704 // imgCaptchaBase64.value = await blobToImageLink(res.data)
705 // console.log(imgCaptchaBase64.value, vCode.value, 'imgCaptchaBase64');
706 // } else {
707 // vCode.value = ''
708 // imgCaptchaBase64.value = ''
709 // }
710 // })
711 // }
712
713 // async function refreshPictureCode() {
714 // getCommoncheckImgCode()
715 // }
716
717 // async function checkPictureCode() {
718 // let validate = await pictureFormRef.value.validate()
719 // if (validate) {
720 // await getSmsCode2();
721 // imgCheckDialog.value = false;
722 // }
723 // }
724
725 // const getSmsCode2 = async () => {
726 // let validate = await registerForm.value.formValidation(['mobileNo'])
727 // if (validate) {
728 // let params = await registerForm.value.getData()
729 // userApi.sendRegisterCode(params.mobileNo);
730 // sendCodeLoading.value = true;
731 // start()
732 // }
733 // }
734
735 // const sendCode = () => {
736 // const formEl = ruleFormRef.value;
737 // if (!formEl) return;
738 // formEl.validate((valid, fields) => {
739 // if (valid) {
740 // centerDialogVisible.value = false;
741 // handleRegister();
742 // }
743 // })
744 // }
745
746 // ! 图形验证弹框
747 const imgCheckDialog = ref(false)
748
749 let LOGINCODE = {
750 grant_type: 'authorization_code',
751 response_type: 'code',
752 client_id: sysConfigStore().getConfig('appKey'),
753 scope: 'other',
754 state: 'authorization-life',
755 loginRedirectUrl: window.location.origin // 登录重定向域名
756 }
757
758
759 const mobileNo = ref('')
760 const logonUser = ref('')
761 /**
762 * 登录前置处理(密码90天验证)
763 */
764 async function beforeLogin() {
765 let validate = await beforeLoginFormRef.value.formValidation();
766 if (!validate) return
767 let formData = await beforeLoginFormRef.value.getData();
768 debugger
769 let _logonUser = formData.logonUser;
770 let password = formData.password;
771 loginStore.encodePwd = CryptoJS.AES.encrypt(password, sysConfigStore().getConfig('appKey')).toString();
772 let loginRes = await idaasStore.login(formData);
773 if (!loginRes) return
774 let res: any = await checkLoginUser(_logonUser)
775 if (res?.code != '00000') {
776 res?.msg && proxy.$message.error(res?.msg);
777 return;
778 }
779 let checkRes = res.data || {};
780 mobileNo.value = checkRes.mobileNo; // 当前用户的手机号
781 logonUser.value = checkRes.logonUser; // 当前用户的手机号
782 // console.log(checkRes, 'checkLoginUser');
783 // 继续登录逻辑
784 function continueLogin() {
785 if (import.meta.env.VITE_verify === 'false' || !checkRes.isCheckSmsValidateCode) {
786 handleLogin({
787 logonUser: logonUser.value,
788 password: password
789 })
790 return
791 }
792 formType.value = 'login';
793 nextTick(() => {
794 loginFormRef.value.setValue({
795 logonUser: logonUser.value,
796 mobileNo: mobileNo.value,
797 password: password
798 })
799 })
800 }
801 // 未到90天校验 pwdValidityDateSurplusDays密码有效期剩余天数
802 if (checkRes && checkRes.pwdValidityDateSurplusDays > 0) {
803 continueLogin()
804 } else {
805 ElMessageBox.confirm("您的密码已超过90天未修改", "提示", {
806 confirmButtonText: "去修改",
807 cancelButtonText: "继续登录",
808 type: "warning",
809 showCancelButton:false,
810 closeOnClickModal: false, // 禁止点击模态框背景关闭
811 closeOnPressEscape: false, // 禁止按下ESC键关闭
812 showClose: false // 可选:隐藏右上角的关闭按钮
813 }).then(() => {
814 changePassword({
815 mobileNo: mobileNo.value,
816 logonUser: logonUser.value,
817 name: checkRes.userName
818 })
819 }).catch(() => {
820 continueLogin()
821 })
822 }
823 }
824
825 /**
826 * 验证用户身份
827 */
828 async function verifyUser() {
829 let validate = await loginFormRef.value.submitForm();
830 if (validate) {
831 let params = await loginFormRef.value.getData();
832 handleLogin(params)
833 }
834 }
835
836 /**
837 * 触发登录服务
838 * @param params
839 */
840 async function handleLogin(params) {
841 loading.value = true
842 let client_id = params.client_id = proxy.$route.query.client_id;
843 let redirect_uri = params.redirect_uri = proxy.$route.query.redirect_uri;
844 loginStore.smsValidateCode = params.smsCode
845 idaasStore.login(params).then((res: any) => {
846 loading.value = false
847 const result = isJsonString(res) ? JSON.parse(res) : res;
848 let scope = result.data.authorities?.map(a => a.authority).join(' ');
849 if (!client_id) {
850 client_id = sysConfigStore().getConfig('appKey');
851 }
852 let hrefOrigin = window.location.origin;
853 if (!redirect_uri) {
854 redirect_uri = hrefOrigin + '/login';
855 }
856 let state = createStateHashCode();
857 let url = `${hrefOrigin}/idaas/oauth2/authorize?response_type=${LOGINCODE.response_type}&client_id=${client_id}&scope=other&state=${state}&redirect_uri=${encodeURIComponent(redirect_uri)}`
858 localStorage.setItem('idaas_code_url', url)
859 window.location.href = url
860 }).catch(() => {
861 loading.value = false
862 })
863 }
864
865 // !注册前的校验
866 // function beforeRegisterCheck(fieldName: string, errMsg?: string) {
867 // return {
868 // validator: (rule: any, value: any, callback: any) => {
869 // if (value && formType.value == 'register') {
870 // userApi.checkUser({ [fieldName]: value }).then(res => {
871 // res ? callback(errMsg ? new Error(errMsg) : new Error(res)) : callback()
872 // })
873 // } else {
874 // callback();
875 // }
876 // },
877 // trigger: 'blur'
878 // }
879 // }
880
881 // async function handleRegister() {
882 // let validate = await registerForm.value.submitForm()
883 // if (!validate) return
884 // let formData = await registerForm.value.getData(false)
885 // userApi.registerTenant(formData).then((res: any) => {
886 // if (res.data.code == proxy.$passCode) {
887 // proxy.$message.success('注册成功')
888 // registerForm.value.setValue({})
889 // }
890 // })
891 // }
892
893 //const logout = ref(proxy.$route.query.logout);
894
895 // // 存储跳转其他系统的url参数
896 // function saveQueryParams() {
897 // let query = proxy.$route.query;
898 // routeStore.fromUrl = query.fromUrl
899 // routeStore.toUrl = query.toUrl
900 // routeStore.backUrl = query.backUrl
901 // console.log(routeStore, 'routeStore');
902 // }
903
904 function setKeyUp() {
905 if (loginButton) {
906 document.onkeyup = event => {
907 console.log(event);
908 if (event.key === 'Enter') {
909 beforeLogin()
910 }
911 }
912 }
913 }
914 const passKeyRegisterloading = ref(false);
915 const passKeyLoginloading = ref(false);
916
917 const createCredential = async () => {
918 let resignForm = await beforeLoginFormRef.value.getData()
919 return registWebAuthn().then((res: any) => {
920 let options = res.data
921 let publicKeyCredentialCreationOptions = {
922 rp: {
923 id: options.rp.id,
924 name: options.rp.name
925 },
926 user: {
927 id: Base64url.decodeBase64url(uuidv4()),
928 name: resignForm.logonUser,
929 displayName: resignForm.logonUser,
930 },
931 challenge: Base64url.decodeBase64url(options.challenge),
932 pubKeyCredParams: options.pubKeyCredParams,
933 timeout: options.timeout,
934 excludeCredentials: options.excludeCredentials.map(credential => {
935 return {
936 type: credential.type,
937 id: Base64url.decodeBase64url(credential.id)
938 }
939 }),
940 authenticatorSelection: {
941 requireResidentKey: false,
942 residentKey: "preferred"
943 },
944 attestation: options.attestation,
945 extensions: options.extensions
946 };
947
948 let credentialCreationOptions = {
949 publicKey: publicKeyCredentialCreationOptions
950 };
951 console.log(credentialCreationOptions)
952 return navigator.credentials.create(credentialCreationOptions);
953 })
954 }
955 const passKeySign = async () => {
956 let validate = await beforeLoginFormRef.value.submitForm()
957 if (validate) {
958 let params = await beforeLoginFormRef.value.getData()
959 checkDeviceTypeRegist({
960 logonUser: params.logonUser,
961 platform: navigator.userAgentData.platform
962 }).then((response: any) => {
963 console.log(response)
964 if (response.data.code === '00000') {
965 createCredential().then((credential: any) => {
966 console.log(credential)
967 let userRQVO = {
968 logonUser: params.logonUser,
969 password: md5(params.password),
970 authenticator: {
971 clientDataJSON: Base64url.encodeBase64url(credential.response.clientDataJSON),
972 attestationObject: Base64url.encodeBase64url(credential.response.attestationObject),
973 clientExtensions: JSON.stringify(credential.getClientExtensionResults()),
974 deviceType: navigator.userAgentData.platform
975 }
976 }
977 passKeyRegisterloading.value = true;
978 signUp(userRQVO).then((result: any) => {
979 passKeyRegisterloading.value = false;
980 if (result?.code === '00000') {
981 formType.value = 'loginByPassKey'
982 proxy.$message.success('注册成功!');
983 } else {
984 result?.msg && proxy.$message.error(result.msg);
985 }
986 }).catch(error => {
987 console.log(error)
988 passKeyRegisterloading.value = false;
989 })
990 })
991 }
992 })
993 }
994 }
995 const loginWebAuthn = () => {
996 return getLoginWebAuthn().then((res: any) => {
997 let options = res.data
998 let publicKeyCredentialRequestOptions = {
999 challenge: Base64url.decodeBase64url(options.challenge),
1000 timeout: options.timeout,
1001 rpId: options.rpId,
1002 allowCredentials: options.allowCredentials.map(credential => {
1003 return {
1004 type: credential.type,
1005 id: Base64url.decodeBase64url(credential.id)
1006 }
1007 }),
1008 userVerification: "required",
1009 extensions: options.extensions
1010 };
1011
1012 let credentialRequestOptions: any = {
1013 publicKey: publicKeyCredentialRequestOptions
1014 };
1015
1016 return navigator.credentials.get(credentialRequestOptions);
1017 });
1018 }
1019 const passKeyLogin = () => {
1020 loginWebAuthn().then((credential: any) => {
1021 let userRQVO = {
1022 clientDataJSON: Base64url.encodeBase64url(credential.response.clientDataJSON),
1023 credentialId: credential.id,
1024 authenticatorData: Base64url.encodeBase64url(credential.response.authenticatorData),
1025 signature: Base64url.encodeBase64url(credential.response.signature),
1026 clientExtensionsJSON: JSON.stringify(credential.getClientExtensionResults()),
1027 }
1028 passKeyLoginloading.value = true;
1029 getWebAuth4jLogin(userRQVO).then(res => {
1030 console.log(res)
1031 passKeyLoginloading.value = false;
1032 const result = typeof res.data == 'string' ? JSON.parse(res.data) : res.data;
1033 if (result.code == '00000') {//第一次初始化登录。
1034 let client_id = proxy.$route.query.client_id;
1035 let redirect_uri = proxy.$route.query.redirect_uri;
1036 if (!client_id) {
1037 client_id = sysConfigStore().getConfig('appKey');
1038 }
1039 let hrefOrigin = window.location.origin;
1040 if (!redirect_uri) {
1041 //redirect_uri = import.meta.env.VITE_redirectUrl
1042 redirect_uri = hrefOrigin + '/login';
1043 }
1044 let state = createStateHashCode();
1045 let url = `${hrefOrigin}/idaas/oauth2/authorize?response_type=${LOGINCODE.response_type}&client_id=${client_id}&scope=other&state=${state}&redirect_uri=${encodeURIComponent(redirect_uri)}`
1046 localStorage.setItem('idaas_code_url', url)
1047 window.location.href = url
1048 }
1049 }).catch(error=>{
1050 console.log(error)
1051 passKeyLoginloading.value = false;
1052 })
1053 });
1054
1055 }
1056
1057 /**
1058 * 直接去获取验证码表单
1059 */
1060 function toCheckSmsCode() {
1061 let isCheckSms = loginStore.isCheckSms;
1062 if (isCheckSms) {
1063 let password = CryptoJS.AES.decrypt(loginStore.encodePwd || '', sysConfigStore().getConfig('appKey')).toString(CryptoJS.enc.Utf8)
1064 let principal = idaasStore.idaasUserInfo.principal
1065 formType.value = 'login';
1066 nextTick(()=>{
1067 loginFormRef.value.setValue({
1068 logonUser: principal?.logonUser,
1069 mobileNo: principal?.mobileNo,
1070 password: password
1071 })
1072 loginStore.isCheckSms = false // 重置状态
1073 })
1074 }
1075 }
1076
1077
1078 onBeforeMount(() => {
1079 // 子系统退出不需要清除门户的token
1080 // if (logout.value && logout.value == '1') {
1081 // userStore.logout({
1082 // logMessage:'logout-正常触发退出登录'
1083 // });
1084 // }
1085 /// saveQueryParams();
1086 toCheckSmsCode();
1087 });
1088
1089 onMounted(()=>{
1090 // new DevicePixelRatio().init();
1091 setKeyUp()
1092 })
1093
1094 onBeforeUnmount(()=>{
1095 document.onkeyup = null
1096 })
1097
1098
1099 </script>
1100
1101 <style lang="scss" scoped>
1102
1103 .main-h {
1104 height: 100%;
1105 }
1106
1107 .login_form {
1108 position: relative;
1109 background: #FFFFFF;
1110 box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.2);
1111 border-radius: 2px;
1112 }
1113
1114 .login-size {
1115 padding: 60px 50px;
1116 width: 440px;
1117 height: 430px;
1118 }
1119
1120 .register-size {
1121 padding: 40px 32px;
1122 width: 548px;
1123 height: 580px;
1124 }
1125
1126 .login-footer {
1127 display: flex;
1128 flex-direction: column;
1129 }
1130
1131 :deep(.el-button--primary) {
1132 background-image: linear-gradient(116deg, #0C48F5 0%, #23D6D1 95%);
1133
1134 &.is-disabled {
1135 background-image: none;
1136 }
1137 }
1138
1139 .login-img {
1140 cursor: pointer;
1141 width: 72px;
1142 height: 72px;
1143 position: absolute;
1144 top: 0;
1145 right: 0;
1146 }
1147
1148 .login-title {
1149 font-family: PingFangSC-Semibold;
1150 font-size: 24px;
1151 color: #212121;
1152 letter-spacing: 0;
1153 line-height: 36px;
1154 font-weight: 600;
1155 }
1156
1157 :deep(.xieyi) {
1158 margin-bottom: 10px;
1159
1160 .el-checkbox__input.is-checked + .el-checkbox__label {
1161 color: #666;
1162 }
1163 }
1164
1165 .bg-banner {
1166 // position: fixed;
1167 // z-index: 1001;
1168 width: 100%;
1169 height: 90%;
1170 background-image: url('../../assets/images/login-bg.png');
1171 background-size: cover;
1172 /* 背景图覆盖整个元素 */
1173 background-position: center;
1174 /* 背景图居中 */
1175 background-repeat: no-repeat;
1176 /* 防止背景图重复 */
1177 }
1178
1179 #login-box {
1180 display: flex;
1181 justify-content: center;
1182 align-items: center;
1183 height: 100%;
1184 // position: absolute;
1185 // top: 50%;
1186 // left: 50%;
1187 // transform: translateX(-50%) translateY(-51%);
1188 // background-color: #fff;
1189
1190 .login-form {
1191 display: flex;
1192 flex-direction: column;
1193 justify-content: center;
1194 min-height: 500px;
1195 width: 368px;
1196 overflow: hidden;
1197
1198 .title-container {
1199 position: relative;
1200
1201 .title {
1202 font-size: 28px;
1203 color: #212121;
1204 letter-spacing: 0;
1205 text-align: center;
1206 line-height: 42px;
1207 font-weight: 600;
1208 margin-bottom: 32px;
1209 }
1210 }
1211 }
1212
1213 :deep(.el-input) {
1214 height: 36px;
1215 }
1216
1217 }
1218
1219 .copyright_text {
1220 width: 100%;
1221 font-size: 12px;
1222 color: #2c3e50;
1223 line-height: 18px;
1224 text-align: center;
1225 font-weight: 400;
1226 position: absolute;
1227 bottom: 2px;
1228 left: 50%;
1229 transform: translateX(-50%);
1230 }
1231
1232
1233 .forget-pwd-btn {
1234 cursor: pointer;
1235 margin-top: 12px;
1236 font-size: 14px;
1237 color: #999999;
1238 line-height: 21px;
1239 font-weight: 400;
1240 }
1241
1242 .code-desc {
1243 font-size: 12px;
1244 color: #999999;
1245 letter-spacing: 0;
1246 line-height: 17px;
1247 font-weight: 400;
1248 margin-top: 8px;
1249 }
1250
1251 .pb-22px {
1252 padding-bottom: 22px;
1253 }
1254
1255 .overflow-auto {
1256 overflow: auto;
1257 }
1258
1259 .pt-30px {
1260 padding-top: 30px;
1261 }
1262
1263 .flex {
1264 display: flex;
1265 }
1266
1267 .loginButton.is-disabled {
1268 background: rgb(159.5, 206.5, 255);
1269 color: #fff;
1270 }
1271 .el-button.is-link:hover {
1272 color: inherit;
1273 }
1274 </style>
1 <!--登录页-->
2 <!--登录页-->
3
4 <style scoped>
5 .main {
6 height: 100%;
7 width: 100%;
8 overflow-y: auto;
9 }
10
11 .content {
12 background: #fff;
13 line-height: 34px;
14 width: 1300px;
15 text-align: left;
16 margin: 20px auto;
17 }
18
19 h1 {
20 font-size: 20px;
21 text-align: center;
22 font-weight: bold;
23 }
24
25 h2 {
26 font-size: 16px;
27 font-weight: bold;
28 margin-top: 8px;
29 margin-bottom: 8px;
30 }
31
32 h3,
33 p {
34 text-indent: 2em;
35 margin-top: 8px;
36 margin-bottom: 8px;
37 }
38
39 h4 {
40 text-indent: 2em;
41 font-weight: normal;
42 margin: 5px 0;
43 }
44 </style>
45
46
47 <template>
48 <div class="main">
49 <div class="content">
50 <h1>用户服务协议</h1>
51 <h2>第一条 重要提示</h2>
52 <p>
53 欢迎申请并使用数据资产运营平台(以下简称“平台”)所提供的各项服务。请您(以下亦称“用户”)在登录平台前,仔细阅读本协议之全部条款,并确认您已完全理解本协议之规定,包括但不限于知识产权条款、法律适用及争议解决条款等涉及您重大权益及义务的条款,如您对本协议有任何疑问,请立即停止登录或使用平台,并可向北京传世博润科技有限公司客服咨询。
54 </p>
55 <h2>第二条 协议的接受与变更</h2>
56 <p>
57 2.1
58 在您充分阅读并且理解本协议,并勾选本协议前选项框或点击登录按钮成功登录,您的行为表示您同意并签署了本协议,表明您确定自己具有接受平台服务的权利和行为能力,并自愿接受本协议全部条款的约束,本协议构成您与北京传世博润科技有限公司(以下简称“我们”)及其经营的平台之间具有约束力的法律文件。
59 </p>
60 <p>
61 2.2
62 我们会根据国家法律法规变化及运营需要不时的对本协议进行修改,修改后的协议一经公布既有效的代替原协议。用户有义务不时关注并阅读最新版本的协议、声明、规则等内容。如果您不同意本协议的修改,请立即停止访问或使用本软件或取消已经获得的服务;如果您选择在本协议修改并公布后继续访问或使用,则视为您同意接受本协议的修改。
63 </p>
64 <p>
65 2.3
66 您与我们签署的本协议所列明的条款,并不能完全涵盖您与我们所有的权利和义务。因此,我们公布的其他声明、规则等均视为本协议之补充协议,为本协议不可分割的组成部分,与本协议具有同等的法律效力。若其他声明、规则等与服务协议存在任何冲突或不一致的,以本协议为准。
67 </p>
68 <h2>第三条 账户的取得与使用</h2>
69 <p>
70 3.1
71 您需确认,在您开始使用平台前,应当具有中华人民共和国法律规定的与您行为相适应的民事行为能力。若您不具备前述与您行为相适应的民事行为能力,则您及您的监护人应按照法律的规定承担因此而导致的一切后果。
72 </p>
73 <p>3.2
74 您有权选择是否成为平台用户。</p>
75 <p>
76 3.3
77 您选择成为平台用户时,应按照平台客服的提示及要求填写或提供资料、信息,并确保资料、信息的真实性、正确性及完整性。如果您的资料、信息发生变化,应及时修改。
78 </p>
79 <p>
80 3.4
81 成为平台用户后将得到一个用户名和密码,用户凭用户名和密码享受平台向其会员用户提供的服务。
82 </p>
83 <p>
84 3.5
85 用户将对用户名和密码安全负全部责任,并且用户对以其用户名进行的所有活动和事件负全责。用户有权根据平台规定的程序修改密码。非经平台书面同意,用户名和密码不得擅自转让或者授权他人使用,否则用户应承担由此造成的一切后果。
86 </p>
87 <p>
88 3.6
89 用户若发现任何非法使用用户账号或存在安全漏洞的情况,请立即通告本公司。</p>
90 <p>
91 3.7
92 为改善用户体验、完善服务内容,我们将不断努力开发新的产品和服务,并为您不时提供软件更新(这些更新可能会采取软件替换、修改、功能强化、版本升级等形式)。为保证本软件安全性和功能的一致性,我们有权不经向您特别通知而对软件进行更新,或者对软件的部分功能效果进行改变或限制。本软件不保证旧版本软件继续可用及相应的客户服务,请您随时核对并下载最新版本。
93 </p>
94
95 <h2>第四条 您的权利和义务</h2>
96 <p>4.1 用户有权利拥有自己在平台的用户名和密码并有权利使用自己的用户名和密码随时登录平台。</p>
97 <p>
98 4.2 用户有权利享受平台提供的互联网技术和信息服务,并有权利在接受平台提供的服务时获得平台的技术支持、咨询等服务。
99 </p>
100 <p>4.3 用户保证不会利用技术或其他手段破坏及扰乱平台。</p>
101 <p>
102 4.4 用户应尊重平台及其他第三方的知识产权和其他合法权利,平台保留用户侵犯平台知识产权时终止向该用户提供服务并不退还任何款项的权利。
103 </p>
104 <p>
105 4.5 对由于用户给平台提供的联络方式有误而导致的一切后果,用户应自行承担责任,包括但不限于因用户未能及时收到平台的相关通知而导致的后果和损失。
106 </p>
107 <p>
108 4.6
109 用户保证其使用平台服务时将遵从国家、地方法律法规、行业惯例和社会公共道德,不会利用平台提供的服务进行存储、发布、传播如下信息和内容:违反国家法律法规政策的任何内容(信息);违反国家规定的政治宣传和/或新闻信息;涉及国家秘密和/或安全的信息;封建迷信和/或淫秽、色情、下流的信息或教唆犯罪的信息;博彩有奖、赌博游戏;违反国家民族和宗教政策的信息;妨碍互联网运行安全的信息;侵害他人合法权益的信息和/或其他有损于社会秩序、社会治安、公共道德的信息或内容。用户同时承诺不得为他人发布上述不符合国家规定和本服务条款约定的信息内容提供任何便利,包括但不限于设置URL、BANNER链接等。用户承认平台有权在用户违反上述时有权终止向用户提供服务并不与退还任何款项,因用户上述行为给平台造成损失的,用户应予赔偿。
110 </p>
111 <h2>第五条 平台的权利和义务</h2>
112 <p>5.1 平台应根据用户类型向用户提供合格的网络技术和信息服务。</p>
113 <p>5.2 平台承诺对用户资料采取对外保密措施,不向第三方披露用户资料,不授权第三方使用用户资料,除非:</p>
114 <h4>(1)依据本协议条款或者用户与 平台之间其他服务协议、合同、在线条款等规定可以提供;</h4>
115 <h4>(2)依据法律法规的规定应当提供;</h4>
116 <h4>(3)行政、司法等有权部门要求本公司提供;</h4>
117 <h4>(4)用户同意平台向第三方提供;</h4>
118 <h4>(5)平台解决举报事件、提起诉讼而提交的;</h4>
119 <h4>(6)平台为防止严重违法行为或涉嫌犯罪行为发生而采取必要合理行动所必须提交的;</h4>
120 <h4>
121 (7)平台为向用户提供产品、服务、信息而向第三方提供的,包括平台通过第三方的技术及服务向用户提供产品、服务、信息的情况。
122 </h4>
123 <p>5.3 平台有权使用用户资料。</p>
124 <p>
125 5.4 平台有权利对用户进行审查并决定是否接受用户成为平台会员或是否与用户进行某一交易。
126 </p>
127 <p>
128 5.5
129 平台保留在用户违反国家、地方法律法规规定或违反本注册条款的情况下终止为用户提供服务并终止用户账号的权利,并且在任何情况下,平台对任何间接、偶然、特殊及继起的损害不负责任。
130 </p>
131 <h2>第六条 免责说明</h2>
132 <p>
133 6.1 我们作为网络服务的提供者,不保证平台系统能充分满足您的需求,您在接受平台服务的过程中,可能遇到的错误、不作为等事件,我们不承担法律责任。
134 </p>
135 <p>
136 6.2
137 您作为平台系统的用户有义务确保在平台系统中录入数据的真实性,我们作为平台应用的提供者,不对您在平台系统中录入数据的真实性承担法律责任。
138 </p>
139 <p>
140 6.3
141 基于互联网的特殊性,我们也不能保证您在使用我们提供的服务过程中,服务不中断,我们对服务的及时性、安全性不作保证,不承担非因我们导致的任何责任。我们竭力保证您对本网站进行安全的访问和使用,但我们不保证网站或服务器不含病毒或其他潜在有害因素。
142 </p>
143 <p>
144 6.4
145 如果我们发现或收到他人举报或投诉您在使用过程中违反任何法律法规、公序良俗或本协议的规定,我们有权要求不经通知随时对账号进行处理,并视情节对违规账号处以包括但不限于警告、限制或禁止使用部分或全部产品及服务,账号封禁直至注销。
146 </p>
147 <p>
148 6.5 您应从本公司官网或其他拥有平台授权的第三方获得安装程序。如果您从未经平台授权的第三方获得平台安装程序或名称相同的安装程序,我们及平台无法保证软件能够正常使用,并对因此给您造成损失不予负责。
149 </p>
150 <p>
151 6.6
152 您理解并同意,因违法法律法规、公序良俗或本协议的规定,导致或产生第三方主张的任何索赔、要求或损失,导致或产生第三方主张的任何索赔、要求或损失,您应当独立承担全部责任。我们或平台因此遭受损失的,您也应当一并赔偿。
153 </p>
154 <h2>第七条 知识产权归属与授权</h2>
155 <p>
156 平台的一切商标权、专利权等知识产权和商业秘密,以及与平台相关的所有信息内容。包括但不限于图标、图片、色彩、界面设计、版面框架、有关数据等均受中华人民共和国著作权法、商标法、专利发、反不正当竞争法和相应的国际条约以及其他知识产权法律法规的保护,除涉及第三方授权的知识产权外,平台享有上述所有知识产权,未经平台书面同意,用户不得为任何营利或非营利性的目的自行实施、利用转让或许可任何第三方实施、利用、转让上述知识产权,平台保留追究上述未经许可的法律责任的权利。您对所使用的软件有其专属性使用权,但不得自行或许可任何第三方复制、修改、出售或衍生产品。
157 </p>
158 <h2>第八条 服务的终止</h2>
159 <p>8.1 用户有权随时申请终止其会员资格。</p>
160 <p>
161 8.2 平台有权根据实际情况决定取消为用户提供服务,视情况确定是否退还用户为该服务所交纳款项的剩余部分,本公司不承担其他任何责任。
162 </p>
163
164 <h2>第九条 法律适用、管辖和其他</h2>
165 <p>
166 9.1
167 本协议的生效、履行、解释及争议解决均适用中华人民共和国法律。您因使用平台而产生或与之相关的一切争议,权利主张或其他思想,均适用中华人民共和国法律。
168 </p>
169 <p>
170 9.2 您与我们及我们经营的平台发生的一切争议,应友好协商,如协商不成的,应提交北京市朝阳区人民法院管辖。
171 </p>
172 <p>
173 9.3
174 我们可能根据运营需要,不时的发布针对包含您在内的用户的相关协议,并可能将该相关协议作为对本协议的补充或修改,使其成为本协议的一部分。请您及时关注并阅读相关协议。
175 </p>
176 <p>9.4 本协议部分条款被视为废止、无效或不可执行,本协议其余条款仍应有效并具有约束力与可执行性。</p>
177 <p>9.5 我们未行使或执行本服务协议任何权利或规定,不构成对前述权利或权益之放弃。</p>
178 <p>
179 【审慎提醒】如您点击“同意”、勾选相关接受提示。则本协议立即生效,并构成您和北京传世博润科技有限公司及其经营的平台之间有约束力的法律文件。
180 </p>
181 </div>
182 </div>
183 </template>
184
185 <script>
186 export default {};
187 </script>
1 <!--登录页-->
2 <!--登录页-->
3
4 <style scoped>
5 .main {
6 height: 100%;
7 width: 100%;
8 overflow-y: auto;
9 }
10
11 .content {
12 background: #fff;
13 line-height: 34px;
14 width: 1300px;
15 text-align: left;
16 margin: 20px auto;
17 }
18
19 h1 {
20 font-size: 20px;
21 text-align: center;
22 font-weight: bold;
23 }
24
25 h2 {
26 font-size: 16px;
27 font-weight: bold;
28 margin-top: 8px;
29 margin-bottom: 8px;
30 }
31
32 h3,
33 p {
34 text-indent: 2em;
35 margin-top: 8px;
36 margin-bottom: 8px;
37 }
38
39 h4 {
40 text-indent: 2em;
41 font-weight: normal;
42 margin: 5px 0;
43 }
44 </style>
45
46
47 <template>
48 <div class="main">
49 <div class="content">
50 <h1>数据资产运营平台隐私政策</h1>
51 <p>
52 【特别提示】数据资产运营平台非常重视用户的隐私和个人信息保护。您在使用数据资产运营平台的产品与服务时,我们可能会收集和使用您的相关信息。我们希望通过本隐私政策向您说明我们在您使用我们的产品、服务时如何收集、使用、保存、共享和转让这些信息,以及我们为您提供的访问、更新、删除和保护这些信息的方式。
53 </p>
54 <p>
55 请您在使用数据资产运营平台的产品及服务前,仔细阅读并充分理解本隐私政策,重点内容我们已经用粗体标识,请您特别关注。在阅读过程中,如果您不同意相关政策或其中任何条款约定,您应立即停止使用平台的产品及服务。您在点击“同意”按钮后,本隐私政策即构成对双方有约束力的法律文件,即表示您同意我们按照本隐私政策收集、使用、处理和存储您的相关信息。如果您对本隐私政策有任何疑问、意见或建议,可通过本隐私政策提供的联系方式与我们联系。
56 </p>
57 <p style="font-weight: 700;">该平台依据TC609-6-2025-01《可信数据空间 技术架构》、TC609-6-2025-14《可信数据空间
58 数字合约技术要求》、TC609-6-2025-15《可信数据空间 使用控制技术要求》、《数据基础设施 参考架构》、《数据基础设施 互联互通基本要求》、《数据基础设施 用户身份管理和接入要求》、《数据基础设施
59 标识要求》、《数据基础设施 接入连接器技术要求》、《数据基础设施 数据目录描述要求》相关标准要求进行开发。</p>
60 <h2>一、适用范围</h2>
61 <p>
62 1、本隐私政策适用于平台的运营主体,具体是指北京传世博润科技有限公司(以下称“我们”)。前述数据资产运营平台产品和服务的使用人在本隐私政策中称为“您”。
63 </p>
64 <p>
65 2、我们深知个人信息对您的重要性,我们将按法律法规要求,采取相应安全保护措施,保护您的个人信息安全。
66 </p>
67 <h2>二、隐私政策的主要内容</h2>
68 <p>
69 1、我们如何收集和使用您的信息
70 </p>
71 <p>2、Cookie和类似技术的使用</p>
72 <p>
73 3、我们如何共享、转让、公开披露您的信息
74 </p>
75 <p>
76 4、我们如何存储和保护您的信息
77 </p>
78 <p>
79 5、您如何管理您的信息
80 </p>
81 <p>
82 6、本隐私政策的适用及更新</p>
83 <p>
84 7、如何联系我们
85 </p>
86
87 <h2>三、隐私政策的具体内容</h2>
88 <h3>1、我们如何收集和使用您的信息</h3>
89 <p>
90 个人信息,是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。本隐私政策中涉及的个人信息包括:个人基本资料(个人姓名、个人电话号码);个人身份信息(身份证信息);个人生物识别信息(面部特征);网络身份标识信息(个人信息主体账号、IP地址、个人数字证书及与前述有关的密码、口令、口令保护答案);个人通讯信息(通讯录);个人常用设备信息(硬件型号、设备MAC地址、操作系统类型、软件列表唯一设备识别码(android
91 ID/IDFA));</p>
92 <p>
93 个人敏感信息,是指一旦泄露、非法提供或滥用可能危害人身和财产安全,极易导致个人名誉、身心健康受到损害或歧视性待遇的个人信息。本隐私政策中涉及的个人敏感信息包括:个人身份信息(身份证);网络身份标识信息(个人信息主体账号、IP地址、个人数字证书及与前述有关的密码、口令、口令保护答案);个人通讯信息(通讯录);个人上网记录(软件使用记录、点击记录、收藏列表);其他信息等。
94 </p>
95 <p>为了向您提供更好的服务,我们根据您所使用服务的不同,收集您与服务相关的不同信息。</p>
96
97 <p style="font-weight: 700;">
98 特别提示您注意,如信息无法单独或结合其他信息识别到您的个人身份,其不属于法律意义上您的个人信息;当您的信息可以单独或结合其他信息识别到您的个人身份时或我们将无法与任何特定个人信息建立联系的数据与其他您的个人信息结合使用时,这些信息在结合使用期间,将作为您的个人信息按照本隐私政策处理与保护。
99 </p>
100 <p>(一)依据法律法规及监管规定履行法定义务</p>
101 <p>
102 针对各项服务,我们需要按照法律法规及监管规定履行相应的法定义务,包括资质审核、采取风险防范措施。当您需要使用数据资产运营平台服务时,为了遵守法律法规对交易资质的要求,对您的身份进行识别,数据资产运营平台可能会收集您的身份信息、联系方式及认证信息。同时,为了验证您提供信息的准确性和完整性,我们可能会与合法留存您的信息的国家机关、金融机构、企事业单位进行核对。如果您不提供前述信息,将无法使用与监管要求相关的部分服务,但不影响您使用我们提供的其他服务。
103 </p>
104 <p>(二)为您提供产品或服务</p>
105 <p>1)注册、登陆</p>
106 <p>当您在平台创建账户进行注册时,您必须向我们提供您的基本信息(手机号码),并设置登录密码。如果您不提供前述信息,则您无法注册/登录平台账户,且无法使用需注册/登录的功能,但不会影响您正常使用无需注册/登录的功能。</p>
107 <p>2)浏览</p>
108 <p>您可在平台上浏览轮播图、资讯信息、产品介绍等。为此,我们可能会收集您使用数据资产运营平台时的设备信息,包括<span
109 style="font-weight: 700;">设备名称、设备型号、唯一设备标识符、操作系统、应用程序版本、内存信息、CPU信息、硬盘序列号、登录IP地址、登录MAC地址、设备分辨率、内网IP地址、公网IP地址、端口号、CPU序列号、浏览器类型</span>以此来为您提供信息展示的最优方案。此外,在您使用浏览功能的过程中,我们会自动收集您使用数据资产运营平台的详细情况,并作为有关的网络日志保存,包括您输入的搜索关键词信息和点击的链接,您浏览的内容及评论信息,访问的日期和时间,以及您请求的网页记录、操作系统、软件版本号、IP信息。如果您不提供前述信息,则您无法使用浏览功能。
110 </p>
111 <p>3)搜索功能</p>
112 <p>
113 当您使用数据资产运营平台提供的搜索功能时,我们会收集您查询的关键字信息以及您在使用数据资产运营平台服务时所浏览的或要求的其他信息和内容详情。为了给您带来更便捷的搜索服务并不断完善数据资产运营平台的产品和服务,我们可能会使用浏览器网络存储机制和应用数据缓存,您搜索的信息我们可能进行本地存储。该等关键词信息通常无法单独识别您的个人身份,不在本隐私政策的限制范围内。只有当您的搜索关键词信息与您的其他信息有联结并可识别您的个人身份时,则在结合使用期间,我们会将您的搜索关键词信息作为您的个人信息,与您的搜索历史记录一同按照本隐私政策对其进行处理与保护。
114 </p>
115 <p>(三)安全运行</p>
116 <p>1)为了保障软件与服务的安全运行,我们会收集您的软件版本号服务日志、手机状态信息。</p>
117 <p>2)如果您不提供上述信息,您将无法享受我们提供的产品与/或服务。</p>
118 <p>(四)您可选择是否授权我们收集和使用您的个人信息的情形</p>
119 <p>为提升您的服务体验及改进服务质量,或者为您推荐更优质或更适合的服务,数据资产运营平台会向您申请下列与个人信息相关的系统权限:</p>
120 <p>1)基于位置信息的个性化推荐服务:我们会在您开启位置权限后访问获取您的位置信息,根据您的位置信息为您提供更契合您需求的页面展示、产品或服务;</p>
121 <p>2)基于相机/摄像头的附加服务:您可在开启相机/摄像头权限后使用该功能进行扫码用于拍摄照片用于上传系统管理功能的需要,包含且不限于如身份验证、资料拍摄等。</p>
122 <p>
123 3)基于详细文件的访问、上传及存储的附加服务:您可在开启对应文件的权限后使用该功能上传您的照片/图片,以实现上传系统管理功能的需要,包含且不限于如企业认证、经办人授权、连接器认证、数据产品管理、数据产品上架等的功能;我们可能会存储您所上传的文件来符合系统管理功能的需要。
124 </p>
125 <p>4)基于麦克风的语音技术相关附加服务:您可在开启麦克风权限后使用麦克风实现替代手工输出文字的操作。请您知晓,即使您已同意开启麦克风权限,我们也仅会在您主动点击客户端内麦克风图标通过麦克风获取语音信息。</p>
126 <p>
127 您理解并同意,上述附加服务可能需要您在您的设备中开启您的位置信息(地理位置)、摄像头(相机)、文件(图片库/存储)、麦克风(语音)的访问权限,以实现这些权限所涉及信息的收集和使用。您可在您的设备“设置”中逐项查看上述权限的状态,并可自行决定这些权限随时的开启或关闭。请您注意,您开启任一权限即代表您授权我们可以收集和使用相关个人信息来为您提供对应服务,您一旦关闭任一权限即代表您取消了授权,我们将不再基于对应权限继续收集和使用相关个人信息,也无法为您提供该权限所对应的服务。但是,您关闭权限的决定不会影响此前基于您的授权所进行的信息收集及使用。
128 </p>
129 <p>(五)其他用途</p>
130 <p>若您提供的信息中含有第三方的个人信息,在向数据资产运营平台提供这些个人信息之前,您需确保您已经取得合法的授权。</p>
131 <p>(六)根据相关法律法规及国家标准,在以下情形中,我们可能会依法收集并使用您的个人信息无需征得您的同意:</p>
132 <p>1)与国家安全、国防安全直接相关的;</p>
133 <p>2)与公共安全、公共卫生、重大公共利益直接相关的;</p>
134 <p>3)与刑事侦查、起诉、审判和判决执行直接相关的;</p>
135 <p>4)出于维护您或他人的生命、财产重大合法权益但又很难得到您本人同意的;</p>
136 <p>5)所收集的个人信息是您自行向社会公众公开的;</p>
137 <p>6)从合法公开披露的信息中收集的个人信息,包括合法的新闻报道、政府信息公开渠道;</p>
138 <p>7)根据您的要求签订和履行合同所必需的;</p>
139 <p>8)用于维护所提供的服务的安全稳定运行所必需的,包括发现、处置产品或服务的故障;</p>
140 <p>9)与我们履行法律法规规定的义务相关的;</p>
141 <p>10)法律法规规定的其他情形。</p>
142 <h3>2、 Cookie和类似技术的使用</h3>
143
144 <p style="text-indent:2em;">
145 为确保网站正常运转,我们会在您的计算机或移动设备上存储名为Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。借助于Cookie,网站能够存储您的登录状态的数据,提升服务/产品质量及优化用户体验。我们不会将
146 Cookie用于本隐私政策所述目的之外的任何用途。
147 </p>
148 <h3>3、我们如何共享、转让、公开披露您的信息</h3>
149 <p>(一)共享</p>
150 <p style="text-indent:2em;">
151 您的个人信息是我们为您提供服务的重要部分,我们会遵循法律规定对您的信息承担保密义务。除以下情形外,我们不会将您的信息披露给第三方:
152 </p>
153 <p style="text-indent:2em;">
154 1)法定情形下的共享:我们可能会根据法律法规规定、诉讼、争议解决需要,或按行政、司法机关依法提出的要求,对外共享您的个人信息。
155 </p>
156 <p style="text-indent:2em;">
157 2)在获取明确同意的情况下共享:获得您的明确同意后,我们会与其他方共享您的个人信息。
158 </p>
159 <p style="text-indent:2em;">
160 3)在您主动选择情况下共享:您通过数据资产运营平台使用的必要信息会共享给您劳动关系所属企业或您使用数据资产运营平台所服务的企业或您授权可共享的企业。
161 </p>
162 <p style="text-indent:2em;">
163 4)为您提供服务的目的,您的信息可能会与我们的关联公司共享,我们只会共享必要的个人信息,且受本隐私政策约束。如果关联公司超出本隐私政策使用及处理您的信息,将再次征求您的授权同意。
164 </p>
165 <p>(二)转让</p>
166 <p style="text-indent:2em;">我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:</p>
167 <p style="text-indent:2em;">1)事先获得您的明确同意;</p>
168 <p style="text-indent:2em;">2)根据法律法规或强制性的行政或司法要求;</p>
169 <p style="text-indent:2em;">
170 3)转让经去标识化处理的个人信息,且确保数据接收方无法重新识别或者关联个人信息主体;
171 </p>
172 <p style="text-indent:2em;">
173 4)在涉及资产转让、收购、兼并、重组或破产时,如果涉及到个人信息转让,我们会向您告知有关情况,并要求新的持有您个人信息的公司、组织继续受本隐私政策的约束。如果变更个人信息使用目的时,我们将要求该公司、组织重新取得您的明示同意。如果破产且无承接方的,我们将对数据做删除处理。
174 </p>
175 <p>(三)公开披露 我们仅会在以下情况下,公开披露您的个人信息:</p>
176 <p style="text-indent:2em;">1)获得您明确同意或基于您的主动选择,我们可能会公开披露您的个人信息;</p>
177 <p style="text-indent:2em;">
178 2)如果我们确定您出现违反法律法规或严重违反数据资产运营平台云平台相关协议及规则的情况,或为保护其他用户或公众的人身财产安全免遭侵害,我们可能依据法律法规或数据资产运营平台的相关协议规则披露关于您的个人信息,包括相关违规行为以及数据资产运营平台已对您采取的措施。
179 </p>
180 <p>(四)共享、转让、公开披露个人信息时事先征得授权同意的例外</p>
181 <p style="text-indent:2em;">
182 以下情形中,共享、转让、公开披露您的个人信息无需事先征得您的授权同意:
183 </p>
184 <p style="text-indent:2em;">1)与国家安全、国防安全有关的;</p>
185 <p style="text-indent:2em;">2)与公共安全、公共卫生、重大公共利益有关的;</p>
186 <p style="text-indent:2em;">3)与犯罪侦查、起诉、审判和判决执行司法或行政执法有关的;</p>
187 <p style="text-indent:2em;">
188 4)出于维护您或其他个人的生命、财产重大合法权益但又很难得到您本人同意的;
189 </p>
190 <p style="text-indent:2em;">5)所收集的个人信息是您自行向社会公众公开的;</p>
191 <p style="text-indent:2em;">
192 6)从合法公开披露的信息中收集的个人信息,包括合法的新闻报道、政府信息公开渠道。
193 </p>
194 <p style="text-indent:2em;">
195 请知悉,根据适用的法律,若我们对个人信息采取技术措施和其他必要措施进行处理,使得数据接收方无法重新识别特定个人且不能复原,则此类处理后数据的共享、转让、公开披露无需另行向您通知并征得您的同意。
196 </p>
197 <h3>4、我们如何存储和保护您的信息</h3>
198 <p>(一)技术措施与数据保护措施</p>
199 <p style="text-indent:2em;">
200 1)我们在保护您的个人信息上将不断努力,争取采取符合业界标准、合理可行的安全防护措施保护您提供的个人信息安全,防止个人信息遭到未经授权访问、公开披露、使用、修改、损坏或丢失。在您的浏览器与服务器之间交换数据时受SSL(Secure Socket Layer)协议加密保护;我们会使用加密技术提高个人信息的安全性;我们会使用受信赖的保护机制防止个人信息遭到恶意攻击;我们会部署访问控制机制,与相关处理数据的员工签署保密协议,尽力确保只有授权人员才可访问个人信息;以及我们会举办安全和隐私保护培训课程,加强员工对于保护个人信息重要性的认识。另外,我们还会聘请外部独立第三方对我们的信息安全管理体系进行评估。
201 </p>
202 <p style="text-indent:2em;">
203 2)我们有行业先进的以数据为核心,围绕数据生命周期进行的数据安全管理体系,从组织建设、制度设计、人员管理、产品技术各方面多维度提升整个系统的安全性。<span style="font-weight: 700;">目前,数据资产运营平台已经通过了国家信息安全等级保护(三级)的测评和备案。</span>
204 </p>
205 <p style="text-indent:2em;">
206 3)互联网并非绝对安全的环境,我们强烈建议您不要使用非数据资产运营平台推荐的通信方式发送个人信息。在使用数据资产运营平台服务进行网上交易时,您不可避免地要向交易对方或潜在的交易对方披露自己的个人信息,包括联络方式或联系地址。请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如果您发现自己的信息发生泄露,请您立即联络我们,以便我们根据您的申请采取相应措施。请注意,您在使用我们服务时自愿共享甚至公开分享的信息,可能会涉及您或他人的个人信息甚至个人敏感信息。请您更加谨慎地考虑,是否在使用我们的服务时共享甚至公开分享相关信息。
207 </p>
208 <p>4)请使用复杂密码,协助我们保证您的账号安全。</p>
209 <p>(二)存储期限</p>
210 <p style="text-indent:2em;">
211 1)我们会采取合理可行的措施,尽力避免收集无关的个人信息。我们只会在达成本隐私政策所述目的所需的期限内保存您的个人信息。数据存储期限为二十年,除非法律有强制的存留要求。如果我们更改数据存储时间,会另行通知您。在您的个人信息超出保留期间后或您要求注销账户后,我们将删除您的个人信息或匿名化处理,法律法规另有规定的除外。
212 </p>
213 <p>
214 (三)我们在中华人民共和国境内运营收集和产生的个人信息,存储在中国境内,以下情形除外:
215 </p>
216 <p style="text-indent:2em;">1)法律法规有明确规定;</p>
217 <p style="text-indent:2em;">2)获得您的明确授权。</p>
218 <p>(四)安全事件处理</p>
219 <p style="text-indent:2em;">
220 在不幸发生个人信息安全事件后,我们将按照法律法规的要求向您告知:安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施。事件相关情况我们将以邮件、信函、电话、推送通知的方式告知您,难以逐一告知个人信息主体时,我们会采取合理、有效的方式发布公告。同时,我们还将按照监管部门要求,上报个人信息安全事件的处置情况。
221 </p>
222 <p style="font-weight: 700;">尽管已经采取了上述合理有效措施,并已经遵守了相关法律规定要求的标准,但请您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全,我们将尽力确保您提供给我们的个人信息的安全性。</p>
223 <p style="font-weight: 700;">您知悉并理解,接入我们的服务所用的系统和通讯网络,有可能因我们可控范围外的因素而出现问题。因此,我们强烈建议您采取积极措施保护个人信息的安全,请根据平台要求必须使用复杂密码以及定期修改密码机制、不将自己的账号密码及相关个人信息透露给他人。</p>
224 <h3>5、 您如何管理您的信息</h3>
225 <p>(一)查询、更正或补充您的个人信息</p>
226 <p>
227 您有权查询、更正或补充您的信息,您可以通过登录数据资产运营平台,进入“用户信息”的方式自行进行查询、更正或补充。
228 </p>
229 <p>(二)删除您的个人信息</p>
230 <p style="text-indent:2em;">
231 1)一般而言,我们只会在法律法规定或必需且最短的时间内保存您的个人信息。
232 </p>
233 <p style="text-indent:2em;">
234 2)对于您的部分个人信息,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以直接进行删除。
235 </p>
236 <p style="text-indent:2em;">
237 3)在以下情形中,您可以登录数据资产运营平台,进入“用户信息-注销登录”,自主发起删除个人信息的请求,或联系数据资产运营平台客服或工作人员,向我们提出删除个人信息的请求,但已做数据匿名化处理或法律法规另有规定的除外:
238 </p>
239 <p style="text-indent:2em;">①如果我们处理个人信息的行为违反法律法规;</p>
240 <p style="text-indent:2em;">②如果我们收集、使用您的个人信息,却未征得您的同意;</p>
241 <p style="text-indent:2em;">③如果我们处理个人信息的行为违反了与您的约定;</p>
242 <p style="text-indent:2em;">④如果您不再使用我们的产品或服务,或您注销了账号。</p>
243 <p style="text-indent:2em;">
244 4)您理解并同意:当您或我们协助您删除相关信息后,因为适用的法律和安全技术,我们可能无法立即从备份系统中删除相应的信息,我们将安全地存储您的个人信息并将其与任何进一步处理隔离,直到备份可以清除或实现匿名。
245 </p>
246 <p>(三)账户注销</p>
247 <p style="text-indent:2em;">
248 您可以通过在数据资产运营平台进行自行注销或联系数据资产运营平台的客服/工作人员进行账户注销。当您符合约定的账户注销条件并注销某项服务相关账户后,您该账户内的所有信息将被清空,我们将不会再收集、使用或对外提供与该账户相关的个人信息,但我们仍需按照监管要求的时间保存您在使用服务期间提供或产生的信息,且在该依法保存的时间内有权机关仍有权依法查询。
249 </p>
250 <p>(四)获取您的个人信息副本</p>
251 <p style="text-indent:2em;">
252 您可以联系数据资产运营平台客服申请获取您已经提供给数据资产运营平台的个人信息副本(包括:基本资料、身份信息)。
253 </p>
254 <p>(五)约束信息系统自动决策</p>
255 <p style="text-indent:2em;">
256 在某些业务功能中,我们可能仅依据信息系统、算法等在内的非人工自动决策机制做出决定。如果这些决定显著影响您的合法权益,您有权联系数据资产运营平台客服要求我们做出解释或要求提供适当的救济方式。
257 </p>
258 <p>(六)响应您的请求</p>
259 <p style="text-indent:2em;">
260 如果您无法自主查询、更正或删除您的个人信息,或者其中部分功能系统不支持的,您均可以通过本隐私政策下方的联系方式与我们联系。为了保障安全,我们可能需要您提供书面请求或以其他方式标明您的身份,我们将在收到您的反馈并在验证您的身份后15天内答复您的请求。
261 </p>
262 <p>
263 (七)如果您发现我们收集、使用您个人信息的行为,违反了法律法规规定或违反了与您的约定,您可联系相应的电话或通过客服,要求删除相应的信息。
264 </p>
265 <p>
266 (八)尽管有上述约定,但按照相关法律法规及国家标准,在以下情形中,我们可能无法响应您的请求:
267 </p>
268 <p style="text-indent:2em;">1)与国家安全、国防安全直接相关的;</p>
269 <p style="text-indent:2em;">2)与公共安全、公共卫生、重大公共利益直接相关的;</p>
270 <p style="text-indent:2em;">3)与刑事侦查、起诉、审判和执行判决直接相关的;</p>
271 <p style="text-indent:2em;">4)有充分证据表明您存在主观恶意或滥用权利的;</p>
272 <p style="text-indent:2em;">5)响应您的请求将导致其他个人、组织的合法权益受到严重损害的;。</p>
273 <p style="text-indent:2em;">
274 6)出于维护您或其他个人的生命、财产重大合法权益但又很难得到您本人授权同意的;。
275 </p>
276 <p style="text-indent:2em;">7)涉及商业秘密的;</p>
277 <p style="text-indent:2em;">8)与我们履行法律法规规定的义务相关的。</p>
278 <h3>6、  本隐私政策的适用及更新。</h3>
279 <p>
280 (一)数据资产运营平台所有服务均适用本隐私政策,除非相关服务已有独立的隐私权政策。
281 </p>
282 <p>
283 (二)必要情况下,我们可能会对《数据资产运营平台隐私政策》进行修订。请您注意,只有在您确认修订后的《数据资产运营平台隐私政策》后,我们才会按照修订后的政策收集、使用、处理和存储您的个人信息。您可以选择不同意修订后的《数据资产运营平台隐私政策》,但可能导致您无法使用数据资产运营平台的全部或部分产品及服务功能。
284 </p>
285 <p>(三)未经您同意,我们不会限制您按照本隐私政策所应享受的权利。</p>
286 <p>(四)发生下列重大变化情形时,我们会适时对本隐私政策进行更新:</p>
287 <p style="text-indent:2em;">1.我们的基本情况发生变化,包括兼并、收购、重组引起的所有者变更;</p>
288 <p style="text-indent:2em;">2.收集、存储、使用个人信息的范围、目的、规则发生变化;</p>
289 <p style="text-indent:2em;">3.对外提供个人信息的对象、范围、目的发生变化;</p>
290 <p style="text-indent:2em;">4.您访问和管理个人信息的方式发生变化;</p>
291 <p style="text-indent:2em;">5.数据安全能力、信息安全风险发生变化;</p>
292 <p style="text-indent:2em;">6.用户询问、投诉的渠道和机制,以及外部纠纷解决机构及联络方式发生变化; </p>
293 <p style="text-indent:2em;">7.其他可能对您的个人信息权益产生重大影响的变化。</p>
294 <p>(五)如果您在本隐私政策更新生效后继续使用相关服务,即表示您已充分阅读、理解并接受更新后的政策并愿意受更新后的政策约束。</p>
295 <h3>7、如何联系我们</h3>
296 <p>
297 (一)如果您对本隐私政策存在任何疑问,或对于您的个人信息处理存在任何投诉、意见,请通过相应的邮箱、客服联系我们,我们会尽快回复,最长不超过15天。
298 </p>
299 <p>(二)如果您对我们的回复不满意,特别是您认为我们的个人信息处理行为损害了您的合法权益,您还可以通过向数据资产运营平台住所地有管辖权的法院提起诉讼来寻求解决方案。
300 </p>
301 <p>(三)产品运营者基本情况</p>
302 <p>产品名称:数据资产运营平台</p>
303 <p>公司名称:北京传世博润科技有限公司</p>
304 <p>
305 注册地址:北京市朝阳区利泽西街6号院3号楼10层1001内18
306 </p>
307 <p>个人信息保护相关负责人联系方式: 027-5990-7980</p>
308 </div>
309 </div>
310 </template>
311
312 <script>
313 export default {};
314 </script>
...@@ -35,11 +35,11 @@ export default ({ mode, command }) => { ...@@ -35,11 +35,11 @@ export default ({ mode, command }) => {
35 changeOrigin: env.VITE_OPEN_PROXY === 'true', 35 changeOrigin: env.VITE_OPEN_PROXY === 'true',
36 rewrite: path => path.replace(/\/api/, ''), 36 rewrite: path => path.replace(/\/api/, ''),
37 }, 37 },
38 '/portal':{ 38 // '/portal':{
39 target: env.VITE_API_PORTALURL, 39 // target: env.VITE_API_PORTALURL,
40 changeOrigin: env.VITE_OPEN_PROXY === 'true', 40 // changeOrigin: env.VITE_OPEN_PROXY === 'true',
41 rewrite: path => path.replace(/\/portal/, ''), 41 // rewrite: path => path.replace(/\/portal/, ''),
42 }, 42 // },
43 '/circulation':{ 43 '/circulation':{
44 target: env.VITE_APP_CIRCULATION, 44 target: env.VITE_APP_CIRCULATION,
45 changeOrigin: env.VITE_OPEN_PROXY === 'true', 45 changeOrigin: env.VITE_OPEN_PROXY === 'true',
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!