dc1a7e5e by lihua

门户首页

1 parent 7731293f
Showing 45 changed files with 2090 additions and 193 deletions
......@@ -101,6 +101,12 @@ VITE_APP_SERVICE_BASEURL = ms-daop-trust-api-service
#数字合约接口,身份认证有关的服务
VITE_APP_DIGITAL_CONTRACT_URL = ms-daop-trust-data-space-service
# 是否启用登录验证
VITE_verify = true
# 应用标识
VITE_appKey = '691689d8e4b0f359c04d204a'
# 本地访问地址
# VITE_API_CIRCULATION_URL = http://localhost:9000/circulation
......
......@@ -133,6 +133,9 @@ VITE_APP_CIRCULATION = http://192.168.6.20:18052/
#数据加工交付
VITE_APP_DATA_DELIVERY = http://192.168.6.20:38052/
# 是否启用登录验证
VITE_verify = true
# 是否在打包时生成 sourcemap
VITE_BUILD_SOURCEMAP = false
# 是否在打包时开启压缩,支持 gzip 和 brotli
......
......@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
Dialog_form: typeof import('./src/components/Dialog/dialog_form.vue')['default']
Dialog_grid: typeof import('./src/components/Dialog/dialog_grid.vue')['default']
Dialog_pane: typeof import('./src/components/Dialog/dialog_pane.vue')['default']
DialogPlus: typeof import('./src/components/DialogPlus/src/DialogPlus.vue')['default']
Drawer: typeof import('./src/components/Drawer/index.vue')['default']
EchartsMap: typeof import('./src/components/EchartsMap/index.vue')['default']
Editor: typeof import('./src/components/Editor/src/Editor.vue')['default']
......@@ -28,13 +29,17 @@ declare module '@vue/runtime-core' {
FileUpload: typeof import('./src/components/FileUpload/index.vue')['default']
FixedActionBar: typeof import('./src/components/FixedActionBar/index.vue')['default']
Form: typeof import('./src/components/Form/index.vue')['default']
FormItem: typeof import('./src/components/FormItem/FormItem.vue')['default']
FormPlus: typeof import('./src/components/FormPlus/src/FormPlus.vue')['default']
GraphTopbar: typeof import('./src/components/RelationNetwork/graphTopbar.vue')['default']
Header: typeof import('./src/components/Header/index.vue')['default']
Hour: typeof import('./src/components/Schedule/component/hour.vue')['default']
ImagePreview: typeof import('./src/components/ImagePreview/index.vue')['default']
ImagesUpload: typeof import('./src/components/ImagesUpload/index.vue')['default']
ImageUpload: typeof import('./src/components/ImageUpload/index.vue')['default']
LineageGraph: typeof import('./src/components/LineageGraph/index.vue')['default']
ListPanel: typeof import('./src/components/ListPanel/index.vue')['default']
Logo: typeof import('./src/components/Logo/index.vue')['default']
LookBpmn: typeof import('./src/components/ApprovalProcess/src/components/LookBpmn.vue')['default']
Month: typeof import('./src/components/Schedule/component/month.vue')['default']
NotAllowed: typeof import('./src/components/NotAllowed/index.vue')['default']
......@@ -46,6 +51,7 @@ declare module '@vue/runtime-core' {
PcasCascader: typeof import('./src/components/PcasCascader/index.vue')['default']
Popover: typeof import('./src/components/Popover/index.vue')['default']
RelationNetwork: typeof import('./src/components/RelationNetwork/index.vue')['default']
Retrievepassword: typeof import('./src/components/Retrievepassword/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Schedule: typeof import('./src/components/Schedule/index.vue')['default']
......
{
"appKey": "67bd3018e4b0cac8f9a5beeb"
}
import request from "@/utils/request";
export const idaasLogin = (params) => request({
url: `/oauth/login`,
method: 'idaasPost',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: params,
})
export const getLoginWebAuthn = () => request({
url: `/webauthn/assertion/options`,
method: 'idaasGet',
})
export const getPictureCode = () => request({
url: `/user/get-picture-code`,
method: 'get',
})
// 通用图形验证码
export const commoncheckImgCode = () => request({
url: `/ms-daop-user-service/user/validate-code/get`,
method: 'get',
responseType: 'blob'
})
// 校验图形验证码
export const checkImgCode = (params) => request({
url: `${import.meta.env.VITE_APP_AUTH_URL}/portal/check`,
method: 'post',
data: params
});
// 获取图形验证码
export const getImgCodeSrc = (params) => request({
url: `${import.meta.env.VITE_APP_AUTH_URL}/portal/get-captcha`,
method: 'get',
params
});
export const checkDeviceTypeRegist = (params) => request({
url: `/web-authn/check-device-type-regist?logonUser=${params.logonUser}&deviceType=${params.platform}`,
method: 'idaasGet'
});
/**
* 校验登录用户账号和密码
* @param logonUser 用户手机号
* @returns
*/
export const checkLoginUser = (logonUser:string) => request({
url: `/ms-daop-user-service/user/check-login-user-password?logonUser=${logonUser}`,
method: 'get'
});
export const registWebAuthn = () => request({
url: `/webauthn/attestation/options`,
method: 'idaasGet'
});
export const signUp = (data) => request({
url: `/web-authn/signup`,
method: 'idaasPost',
data: data
});
/** 发送登录验证码到手机 */
export const sendLoginCode = (mobileNo:string) => request({
url: `/ms-daop-user-service/user/get-login-sms-validate-code?mobileNo=${mobileNo}`,
method: 'get'
});
export const getWebAuth4jLogin = (data) => request({
url: `/webauthn/login`,
method: 'idaasPost',
data: data
});
\ No newline at end of file
import DialogPlus from './src/DialogPlus.vue'
export { DialogPlus }
<script setup lang="ts">
import { ElDialog, ElScrollbar } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { computed, useAttrs, ref, unref, useSlots, watch, nextTick } from 'vue'
const slots = useSlots()
const props = defineProps({
modelValue: propTypes.bool.def(false),
scrollbar: propTypes.bool.def(true),
title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true),
maxHeight: propTypes.oneOfType([String, Number]).def('400px'),
minHeight: propTypes.oneOfType([String, Number]).def('140px')
})
const getBindValue = computed(() => {
const delArr: string[] = ['fullscreen', 'title', 'maxHeight']
const attrs = useAttrs()
const obj = { ...attrs, ...props }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
})
const isFullscreen = ref(false)
const isNumber = (val) => {
return val && typeof props.maxHeight == 'number'
}
const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
watch(
() => isFullscreen.value,
async (val: boolean) => {
await nextTick()
if (val) {
const windowHeight = document.documentElement.offsetHeight
dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
} else {
dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
}
},
{
immediate: true
}
)
const dialogStyle = computed(() => {
return {
height: unref(dialogHeight),
'max-height': isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight,
'min-height': isNumber(props.minHeight) ? `${props.minHeight}px` : props.minHeight,
}
})
</script>
<template>
<ElDialog v-bind="getBindValue" :fullscreen="isFullscreen" destroy-on-close lock-scroll draggable top="15vh"
:close-on-click-modal="false" :show-close="true">
<template #header="{ close }">
<div class="flex justify-between items-center h-54px relative">
<slot name="title">
{{ title }}
</slot>
<!-- <div class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]">
<Icon v-if="fullscreen" class="cursor-pointer is-hover !h-54px mr-10px"
:icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
color="var(--el-color-info)" hover-color="var(--el-color-primary)" @click="toggleFull" />
<Icon class="cursor-pointer is-hover !h-54px" icon="ep:close" hover-color="var(--el-color-primary)"
color="var(--el-color-info)" @click="close" />
</div> -->
</div>
</template>
<ElScrollbar v-if="scrollbar" :style="dialogStyle">
<slot></slot>
</ElScrollbar>
<div v-else :style="dialogStyle">
<slot></slot>
</div>
<template v-if="slots.footer" #footer>
<slot name="footer"></slot>
</template>
</ElDialog>
</template>
<style lang="scss" scoped>
.el-overlay-dialog {
display: flex;
justify-content: center;
align-items: center;
:deep .el-dialog {
margin: 0 !important;
:deep .el-dialog__header {
padding: 0 20px;
height: 50px;
border-bottom: 1px solid #d9d9d9;
margin-right: 0;
display: block !important;
align-items: center;
.el-dialog__headerbtn {
width: 50px;
height: 50px;
top: 0;
}
}
&__header {
height: 54px;
padding: 0;
margin-right: 0 !important;
border-bottom: 1px solid var(--el-border-color);
}
&__body {
padding: 15px !important;
}
&__footer {
border-top: 1px solid var(--el-border-color);
}
&__headerbtn {
top: 0;
}
}
}
</style>
<script setup lang="ts">
import { FormPlus, FormSchema } from '@/components/FormPlus'
import { useForm } from '@/hooks/useForm'
import { propTypes } from "@/utils/propTypes";
import { reactive, unref, ref, computed } from 'vue'
import { ElButton, ElInput, FormItemProp } from 'element-plus'
import { useValidator } from '@/hooks/useValidator'
import { ComponentRef } from '@/types/global';
const props = defineProps({
schemaParam: {
type: Object as PropType<FormSchema[]>,
default: () => {
return []
}
},
labelPosition: propTypes.string.def('top'),
disabled: propTypes.bool.def(false),
model: {
type: Object as PropType<any>,
default: () => {
return {}
}
}
})
const formModel = computed(() => {
console.log(unref(props.model), 'model');
return props.model
})
const schemaData = computed(() => reactive<FormSchema[]>(unref(props.schemaParam)))
const { scrollToError } = useValidator()
const { formRegister, formMethods } = useForm()
const {
getFormResult,
setProps,
setValues,
setSchema,
getComponentExpose,
getFormItemExpose,
getElFormExpose,
getFormData
} = formMethods
/**
* 表单根据字段校验
* @param fields 待验证的字段
*/
const formValidation = async (fields: string[] = []) => { // 表单验证
const elFormExpose = await getElFormExpose()
return new Promise(async (resolve) => {
elFormExpose?.validateField(fields, (isValid) => {
if (isValid) {
resolve(isValid)
} else {
resolve(isValid)
}
})
})
}
const clearValidate = async (fields: string[] = []) => { // 表单验证
const elFormExpose = await getElFormExpose()
elFormExpose?.clearValidate(fields)
}
const inoutFocus = async () => {
const inputEl: ComponentRef<typeof ElInput> = await getComponentExpose('field1')
inputEl?.focus()
}
const inoutValidation = async () => {
const formItem = await getFormItemExpose('field1')
const inputEl: ComponentRef<typeof ElInput> = await getComponentExpose('field1')
inputEl?.focus()
formItem?.validate('focus', (val: boolean) => {
console.log(val)
})
}
const formValidate = (prop: FormItemProp, isValid: boolean, message: string) => {
// console.log(prop, isValid, message)
}
/**
* @param filterEmptyVal 是否过滤空值
*/
const getData = (filterEmptyVal = true) => {
if (filterEmptyVal) {
return new Promise(async (resolve) => {
const resultForm = await getFormData(filterEmptyVal)
resolve(resultForm)
})
} else {
return new Promise(async (resolve) => {
const resultForm = await getFormData(filterEmptyVal)
let res = getFormResult(resultForm, schemaData.value)
resolve(res)
})
}
}
const setValue = async (param = {}, reset = false) => {
const elFormExpose = await getElFormExpose()
if (reset) {
elFormExpose?.resetFields()
} else {
setValues(param)
}
}
const submitForm = () => {
return new Promise(async (resolve) => {
const elFormExpose = await getElFormExpose()
scrollToError()
elFormExpose?.validate((isValid) => {
if (isValid) {
resolve(isValid)
} else {
resolve(isValid)
}
})
})
}
// 子组建暴露的方法
defineExpose({
formValidation,
getFormResult,
clearValidate,
setSchema,
submitForm,
getData,
setValue: setValue,
setValues: setValue,
formMethods
})
</script>
<template>
<div>
<FormPlus :schema="schemaData" :disabled="disabled" :model="formModel" :isCol="true" :labelPosition="labelPosition"
@register="formRegister" @validate="formValidate" />
</div>
</template>
<style lang="scss" scoped>
.el-button {
margin-top: 10px;
}
</style>
import FormItem from './FormItem.vue';
export default FormItem
import FormPlus from './src/FormPlus.vue'
import type { FormSchema, FormSetProps } from './src/types'
export type {
ComponentNameEnum,
ComponentName,
InputComponentProps,
AutocompleteComponentProps,
InputNumberComponentProps,
SelectOption,
SelectComponentProps,
SelectV2ComponentProps,
CascaderComponentProps,
SwitchComponentProps,
RateComponentProps,
ColorPickerComponentProps,
TransferComponentProps,
RadioOption,
RadioGroupComponentProps,
RadioButtonComponentProps,
CheckboxOption,
CheckboxGroupComponentProps,
DividerComponentProps,
DatePickerComponentProps,
DateTimePickerComponentProps,
TimePickerComponentProps,
TimeSelectComponentProps,
ColProps,
FormSetProps,
FormItemProps,
FormSchema,
FormProps,
PlaceholderModel,
InputPasswordComponentProps,
TreeSelectComponentProps
} from './src/types'
export interface FormExpose {
setValues: (data: any) => void
setProps: (props: any) => void
delSchema: (field: string,deleteField:boolean) => void
addSchema: (formSchema: FormSchema, index?: number) => void
setSchema: (schemaProps: FormSetProps[]) => void
formModel: any
getComponentExpose: (field: string) => any
getFormItemExpose: (field: string) => any
}
export { FormPlus }
interface RenderHeader {
label:string // 列label
labelClass?:string
style?:string
iconProps?:IconProps
popoverProps:PopoverProps
}
interface IconProps {
iconClass?:string // 图标类名
icon:string // 图标名称
}
interface PopoverProps {
visible?:boolean
popoverContent:string // 提示内容
popoverWidth?:number // 提示框宽度
popoverTrigger? : 'click' | 'focus' | 'hover' | 'contextmenu' // 触发方式
popoverTitle?:string // 提示标题
}
export const useRenderAmountInput = () => {
/**
* 金额input渲染函数
* @param param
* @param isDetail 是否详情
* @returns
*/
const renderAmountInput = (param:RenderHeader,isDetail?:boolean) => {
return (
<>
</>
);
}
return {
renderAmountInput
}
}
\ No newline at end of file
import { FormSchema, ComponentNameEnum, CheckboxGroupComponentProps } from '../types'
import { ElCheckbox, ElCheckboxButton } from 'element-plus'
import { defineComponent } from 'vue'
export const useRenderCheckbox = () => {
const renderCheckboxOptions = (item: FormSchema) => {
// 如果有别名,就取别名
const componentProps = item?.componentProps as CheckboxGroupComponentProps
const valueAlias = componentProps?.props?.value || 'value'
const labelAlias = componentProps?.props?.label || 'label'
const disabledAlias = componentProps?.props?.disabled || 'disabled'
const Com = (
item.component === ComponentNameEnum.CHECKBOX_GROUP ? ElCheckbox : ElCheckboxButton
) as ReturnType<typeof defineComponent>
return componentProps?.options?.map((option) => {
const { value, ...other } = option
return (
<Com
{...other}
disabled={option[disabledAlias || 'disabled']}
label={option[valueAlias || 'value']}
>
{option[labelAlias || 'label']}
</Com>
)
})
}
return {
renderCheckboxOptions
}
}
import { FormSchema, ComponentNameEnum, RadioGroupComponentProps } from '../types'
import { ElRadio, ElRadioButton, ElIcon } from 'element-plus'
import { defineComponent } from 'vue'
import { Check } from "@element-plus/icons-vue"
import '../styles/radio.scss'
export const useRenderRadio = () => {
const renderRadioOptions = (item: FormSchema, formModel: any) => {
// 如果有别名,就取别名
const { field } = item
const componentProps = item?.componentProps as RadioGroupComponentProps
const valueAlias = componentProps?.props?.value || 'value'
const labelAlias = componentProps?.props?.label || 'label'
const disabledAlias = componentProps?.props?.disabled || 'disabled'
const Com = (
item.component === ComponentNameEnum.RADIO_GROUP ? ElRadio : ElRadioButton
) as ReturnType<typeof defineComponent>
return componentProps?.options?.map((option) => {
const { value, ...other } = option
return (
<div class={other.border ? 'radio_panel' : ''}>
<Com
{...other}
disabled={option[disabledAlias || 'disabled']}
label={option[valueAlias || 'value']}
style='margin-right:8px'
>
{option[labelAlias || 'label']}
{
other.border ?
(<ElIcon class={ formModel.value[field] == value ? 'active corner_mark':'corner_mark' }>
<Check></Check>
</ElIcon>) : undefined
}
</Com>
</div>
)
})
}
return {
renderRadioOptions
}
}
import { ElOption, ElOptionGroup } from 'element-plus'
import { FormSchema, SelectComponentProps, SelectOption } from '../types'
export const useRenderSelect = () => {
// 渲染 select options
const renderSelectOptions = (item: FormSchema) => {
const componentsProps = item?.componentProps as SelectComponentProps
const optionGroupDefaultSlot = componentsProps?.slots?.optionGroupDefault
// 如果有别名,就取别名
const labelAlias = componentsProps?.props?.label
const keyAlias = componentsProps?.props?.key
return componentsProps?.options?.map((option) => {
if (option?.options?.length) {
return optionGroupDefaultSlot ? (
optionGroupDefaultSlot(option)
) : (
<ElOptionGroup label={option[labelAlias || 'label']} key={option[keyAlias || 'key']}>
{{
default: () =>
option?.options?.map((v) => {
return renderSelectOptionItem(item, v)
})
}}
</ElOptionGroup>
)
} else {
return renderSelectOptionItem(item, option)
}
})
}
// 渲染 select option item
const renderSelectOptionItem = (item: FormSchema, option: SelectOption) => {
// 如果有别名,就取别名
const componentsProps = item.componentProps as SelectComponentProps
const labelAlias = componentsProps?.props?.label
const valueAlias = componentsProps?.props?.value
const keyAlias = componentsProps?.props?.key
const optionDefaultSlot = componentsProps.slots?.optionDefault
return (
<ElOption
{...option}
key={option[keyAlias || 'key']}
label={option[labelAlias || 'label']}
value={option[valueAlias || 'value']}
disabled={option.disabled}
>
{{
default: () => (optionDefaultSlot ? optionDefaultSlot(option) : undefined)
}}
</ElOption>
)
}
return {
renderSelectOptions
}
}
import type { Component } from 'vue'
import {
ElCascader,
ElCheckboxGroup,
ElColorPicker,
ElDatePicker,
ElInput,
ElInputNumber,
ElRadioGroup,
ElRate,
ElSelect,
ElSelectV2,
ElSlider,
ElSwitch,
ElTimePicker,
ElTimeSelect,
ElTransfer,
ElAutocomplete,
ElDivider,
ElTreeSelect,
ElUpload
} from 'element-plus'
// import { InputPassword } from '@/components/InputPassword'
// import { Editor } from '@/components/Editor'
// import { JsonEditor } from '@/components/JsonEditor'
// import { IconPicker } from '@/components/IconPicker'
import { ComponentName } from '../types'
import { Recordable } from '@/types/global'
const componentMap: Recordable<Component, ComponentName> = {
RadioGroup: ElRadioGroup,
RadioButton: ElRadioGroup,
CheckboxGroup: ElCheckboxGroup,
CheckboxButton: ElCheckboxGroup,
Input: ElInput,
Autocomplete: ElAutocomplete,
InputNumber: ElInputNumber,
Select: ElSelect,
Cascader: ElCascader,
Switch: ElSwitch,
Slider: ElSlider,
TimePicker: ElTimePicker,
DatePicker: ElDatePicker,
Rate: ElRate,
ColorPicker: ElColorPicker,
Transfer: ElTransfer,
Divider: ElDivider,
TimeSelect: ElTimeSelect,
SelectV2: ElSelectV2,
TreeSelect: ElTreeSelect,
Upload: ElUpload,
// InputPassword: InputPassword,
// Editor: Editor,
// JsonEditor: JsonEditor,
// IconPicker: IconPicker
}
export { componentMap }
import { firstUpperCase, humpToDash } from '@/utils/common'
import { PlaceholderModel, FormSchema, ComponentNameEnum, ColProps } from '../types'
import { set, get } from 'lodash-es'
import { Recordable } from '@/types/global'
/**
*
* @param schema 对应组件数据
* @returns 返回提示信息对象
* @description 用于自动设置placeholder
*/
export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
const textMap = [
ComponentNameEnum.INPUT,
ComponentNameEnum.AUTOCOMPLETE,
ComponentNameEnum.INPUT_NUMBER,
ComponentNameEnum.INPUT_PASSWORD
]
const selectMap = [
ComponentNameEnum.SELECT,
ComponentNameEnum.TIME_PICKER,
ComponentNameEnum.DATE_PICKER,
ComponentNameEnum.TIME_SELECT,
ComponentNameEnum.SELECT_V2
]
if (textMap.includes(schema?.component as ComponentNameEnum)) {
return {
placeholder: '请输入'
}
}
if (selectMap.includes(schema?.component as ComponentNameEnum)) {
// 一些范围选择器
const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange']
if (
twoTextMap.includes(
((schema?.componentProps as any)?.type ||
(schema?.componentProps as any)?.isRange) as string
)
) {
return {
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
rangeSeparator: '-'
}
} else {
return {
placeholder: '请选择'
}
}
}
return {}
}
/**
*
* @param col 内置栅格
* @returns 返回栅格属性
* @description 合并传入进来的栅格属性
*/
export const setGridProp = (col: ColProps = {}): ColProps => {
const colProps: ColProps = {
// 如果有span,代表用户优先级更高,所以不需要默认栅格
...(col.span
? {}
: {
xs: 24,
sm: 12,
md: 12,
lg: 12,
xl: 12
}),
...col
}
return colProps
}
/**
*
* @param item 传入的组件属性
* @param props 传入表单所有属性
* @returns 默认添加 clearable 属性
*/
export const setComponentProps = (item: FormSchema,props:any): Recordable => {
// const notNeedClearable = ['ColorPicker']
// 拆分事件并组合
const onEvents = (item?.componentProps as any)?.on || {}
const newOnEvents: Recordable = {}
for (const key in onEvents) {
if (onEvents[key]) {
newOnEvents[`on${firstUpperCase(key)}`] = (...args: any[]) => {
onEvents[key](...args)
}
}
}
const componentProps: Recordable = {
clearable: true, // 默认全局开启清空属性
...item.componentProps,
...newOnEvents
}
// 需要删除额外的属性
if (componentProps.slots) {
delete componentProps.slots
}
if (componentProps.on) {
delete componentProps.on
}
if ((item.component === 'Select' || item.component === 'Cascader' || item.component === 'TreeSelect') && (componentProps.filterable === undefined)) {
componentProps.filterable = true
}
if ((item.component === 'Select' || item.component === 'TreeSelect') && (componentProps.fitInputWidth === undefined)) {
componentProps.fitInputWidth = true
}
if ((item.component === 'InputNumber') && (componentProps.controls === undefined)) { // 默认全局取消加减号按钮
componentProps.controls = false
}
if ((item.component === 'InputNumber') && componentProps.precision === undefined) { // 默认全局2位小数
componentProps.precision = (props.disabled || componentProps.disabled) ? 3 : 2
}
if ((item.component === 'InputNumber') && componentProps.min === undefined) { // 默认默认最小值0
componentProps.min = 0
}
// if ((item.component === 'Input') && componentProps.formatter === undefined) { // 默认添加过滤前后空格
// componentProps.formatter = (value:string) => value.trim()
// }
return componentProps
}
/**
*
* @param formModel 表单数据
* @param slotsProps 插槽属性
*/
export const setItemComponentSlots = (slotsProps: Recordable = {}): Recordable => {
const slotObj: Recordable = {}
for (const key in slotsProps) {
if (slotsProps[key]) {
if (typeof slotsProps[key] == 'function') {
slotObj[humpToDash(key)] = (...args: any[]) => {
return slotsProps[key]?.(...args)
}
} else {
slotObj[humpToDash(key)] = () => {
return slotsProps[key]
}
}
}
}
return slotObj
}
/**
*
* @param schema Form表单结构化数组
* @param formModel FormMoel
* @returns FormMoel
* @description 生成对应的formModel
*/
export const initModel = (schema: FormSchema[], formModel: Recordable) => {
const model: Recordable = { ...formModel }
schema.map((v) => {
if (v.remove) {
delete model[v.field]
} else if (v.component !== 'Divider') {
// const hasField = Reflect.has(model, v.field)
const hasField = get(model, v.field)
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
set(
model,
v.field,
hasField !== void 0 ? get(model, v.field) : v.value !== void 0 ? v.value : undefined
)
// model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : undefined
}
})
return model
}
.radio_panel {
.el-radio {
&.is-bordered {
padding: 0;
position: relative;
border-radius:2px;
.el-radio__input {
position: absolute;
opacity: 0;
}
.el-radio__label {
padding: 0 15px;
font-size: 14px;
position: relative;
.corner_mark {
position: absolute;
right: 0;
bottom: -4px;
width: 18px;
height: 18px;
color: #fff;
svg {
width: 9px;
height: 9px;
position: absolute;
right: 0;
bottom: 2px;
z-index: 1;
}
&.active {
&::after {
content: "";
position: absolute;
border: 9px solid var(--el-color-primary);
border-top-color: transparent;
border-left-color: transparent;
}
}
}
}
.el-radio__input.is-checked+.el-radio__label {
color: var(--el-color-primary);
}
}
}
}
\ No newline at end of file
<template>
<div>
<DialogPlus modal-class="auth-user" append-to-body v-model="dialogVisible" @open="openedDialog" width="460px"
maxHeight="250px" :close-on-click-modal="false" title="用户身份认证">
<div class="select-tenant">
<el-form v-show="formType == 'validate'" label-position="top" ref="registerFormRef" :model="registerForm"
:rules="registerRules" class="login-form" auto-complete="on">
<el-form-item prop="logonUser" label="账号">
<el-input v-model.trim="registerForm.logonUser" :disabled="isModifypassword" placeholder="请输入账号" text tabindex="1" autocomplete="on">
</el-input>
</el-form-item>
<el-form-item prop="mobileNo" label="手机号">
<el-input v-model.trim="registerForm.mobileNo" :disabled="isModifypassword" placeholder="请输入手机号" text tabindex="1" autocomplete="on">
</el-input>
</el-form-item>
<el-form-item prop="validateCode" label="图形验证码">
<el-input class="captcha" v-model.trim="registerForm.validateCode" placeholder="请输入图形验证码" tabindex="2"
autocomplete="on">
<template #append>
<img class="h-26px" :src="imgCaptchaBase64" @click="refreshPictureCode" />
</template>
</el-input>
</el-form-item>
</el-form>
<el-form v-show="formType === 'reset'" label-position="top" ref="resetFormRef" :model="resetForm" :rules="resetRules"
autocomplete="off" class="login-form" auto-complete="on">
<el-form-item prop="smsCode" autocomplete="off" label="短信验证码">
<el-input v-model.trim="resetForm.smsCode" autocomplete="off" name="captcha-unique123"
:placeholder="`请输入${registerForm.mobileNo.substring(0, 3)}****${registerForm.mobileNo.substring(7)}收到的短信验证码`">
<template #append>
<span>{{ `${timeLeft}s` }}</span>
</template>
</el-input>
<div class="code-desc">
<span>若手机未收到验证码,请</span>
<el-button link type="primary" size="small" @click="recertification"
style="padding: 0px;vertical-align: baseline;">重新认证</el-button>
</div>
</el-form-item>
<el-form-item prop="pwd" label="新密码" :error="errorPsw">
<!-- <el-input v-model.trim="resetForm.pwd" type="password" placeholder="请输入新密码" show-password autocomplete="off"
autocomplete="new-password">
</el-input> -->
<PasswordStrengthMeter v-model.trim="password" placeholder="请输入新密码" @change="changePwd" style="width: 100%;"></PasswordStrengthMeter>
</el-form-item>
<el-form-item prop="checkPwd" label="确认新密码">
<el-input v-model.trim="resetForm.checkPwd" type="password" placeholder="请再次输入新密码" show-password
autocomplete="new-password">
</el-input>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="closeDialog"> </el-button>
<el-button v-show="formType === 'validate'" :loading="sendCodeLoading" type="primary"
@click="handleSendCaptcha">发送验证码至手机</el-button>
<el-button v-show="formType === 'reset' && timeLeft > 0" :loading="saveLoading" type="primary"
:disabled="!resetForm.smsCode || !resetForm.pwd || !resetForm.checkPwd"
@click.prevent="handleReset">修改密码</el-button>
<el-button v-show="formType === 'reset' && timeLeft == 0" :loading="saveLoading" type="primary"
@click.prevent="recertification">重新认证</el-button>
</template>
</DialogPlus>
</div>
</template>
<script lang="ts" setup>
import { useValidator } from '@/hooks/useValidator';
import useCountdown from '@/hooks/useCountdown'
import type { FormRules } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import useIdaasStore from '@/store/modules/idaas';
import {
getPictureCode
} from "@/api/modules/idaas"
import useLogin from '@/store/modules/login'
import PasswordStrengthMeter from '../PasswordStrengthMeter/index.vue'
import { autoSalt } from '@/utils/common';
// const AsyncPasswordStrengthMeter = defineAsyncComponent(() =>
// import('../PasswordStrengthMeter/index.vue')
// );
const loginStore = useLogin()
const idaasStore = useIdaasStore()
const { required, phone } = useValidator();
const { proxy } = getCurrentInstance() as any;
const mobileNo = computed(()=> idaasStore.idaasUserInfo.principal?.mobileNo || '')
const logonUser = computed(()=> idaasStore.idaasUserInfo.principal?.logonUser || '')
const props = defineProps({
schemaInfo: {
type: Object as PropType<{
visible: boolean;
}>,
default: () => { }
},
operate:propTypes.string.def('modifypassword'),
});
const isModifypassword = computed(() => props.operate === 'modifypassword')
const dialogVisible = computed({
get: () => {
return props.schemaInfo.visible;
},
set: (val) => {
props.schemaInfo.visible = val
}
})
function openedDialog() {
recertification()
getPictureCodeInfo()
}
function closeDialog() {
dialogVisible.value = false
}
// 重新认证
const recertification = () => {
formType.value = 'validate';
clearForm()
reset()
}
function clearForm() {
let _mobileNo = isModifypassword.value ? mobileNo.value : ''
let _logonUser = isModifypassword.value ? logonUser.value : ''
password.value = ''
resetForm.value = {
smsCode: '',
pwd: '',
checkPwd: ''
}
registerForm.value = {
logonUser: _logonUser,
mobileNo: _mobileNo,
validateCode: ''
}
}
// ! 修改密码逻辑
const formType = ref('validate')
const resetFormRef = ref()
const registerFormRef = ref()
const registerRules = ref<FormRules>({
logonUser: [
{ required: true, trigger: 'blur', message: '请输入账号' },
],
mobileNo: [
required('请输入手机号'), phone()
],
validateCode: [
{ required: true, trigger: 'blur', message: '请输入图形验证码' },
]
})
const registerForm = ref({
logonUser: '',
mobileNo: '',
validateCode: ''
})
function changePwd() {
resetForm.value.pwd = password.value
}
const errorPsw = computed(() => loginStore.firstUnmetRequirement ? `需要${loginStore.firstUnmetRequirement}` : null)
const password = ref('')
/**
* 校验新密码
*/
function validatorPassword(rule, value, callback) {
console.log(value,loginStore.firstUnmetRequirement);
if (loginStore.firstUnmetRequirement) {
callback(new Error(`需要${loginStore.firstUnmetRequirement}`))
} else {
callback();
}
}
/**
* 校验确认密码
*/
function validatorConfirmpwd(rule, value, callback) {
if (value != resetForm.value.pwd) {
callback(new Error('密码不一致,请重新输入'))
} else {
callback();
}
}
const resetRules = ref<FormRules>({
smsCode: [
// 不显示验证信息了,只有填写完整,保存按钮才会被使用。
// {required: true, trigger: 'blur', message: '请输入收到的短信验证码' },
],
pwd: [
{
validator: validatorPassword,
trigger: []
}
],
checkPwd: [
{
validator: validatorConfirmpwd,
trigger: ['change', 'blur']
}
]
})
const resetForm = ref({
smsCode: '',
pwd: '',
checkPwd: ''
})
let promise: any = ref(null);
/** 图形验证码图片。 */
const imgCaptchaBase64 = ref('');
/** 图形验证码guid */
const validateCodeGuid = ref('');
/** 发送短信验证码的loading */
const sendCodeLoading = ref(false);
/** 重置密码保存的loading */
const saveLoading = ref(false);
const { timeLeft, minutes, seconds, start, stop, reset } = useCountdown(300);
watchEffect(() => {
console.log(timeLeft.value, 'timeLeft');
if (timeLeft.value == 0) {
stop()
}
})
const refreshPictureCode = () => {
if (promise.value) {
return;
}
getPictureCodeInfo()
}
function getPictureCodeInfo() {
promise.value = getPictureCode().then((res: any) => {
promise.value = null;
if (res.data.code == proxy.$passCode) {
imgCaptchaBase64.value = res.data.data?.imageBase64 || "";
validateCodeGuid.value = res.data.data?.guid || "";
}
});
}
const handleSendCaptcha = () => {
registerFormRef.value && registerFormRef.value.validate((valid) => {
if (valid) {
sendCodeLoading.value = true
let params: any = { ...registerForm.value };
params.validateCodeGuid = validateCodeGuid.value;
idaasApi.sendCode(params).then((res: any) => {
sendCodeLoading.value = false;
if (res?.data.code == proxy.$passCode) {
proxy.$ElMessage.success('验证码发送成功!');
loginStore.firstUnmetRequirement = ''
formType.value = 'reset';
resetForm.value.smsCode = '';
resetForm.value.pwd = "";
resetForm.value.checkPwd = "";
start()
} else {
refreshPictureCode();
registerForm.value.validateCode = "";
}
});
}
});
}
async function handleReset() {
let res = await resetFormRef.value?.validate()
if (!res) return
let params = Object.assign({}, resetForm.value, { mobileNo: registerForm.value.mobileNo, logonUser: registerForm.value.logonUser });
params.pwd = autoSalt(params.pwd, false, false);
idaasApi.resetPwd(params).then((res: any) => {
if (res.data.code == proxy.$passCode) {
proxy.$ElMessage.success('密码重置成功');
dialogVisible.value = false;
userApi.recordUpdateTime(registerForm.value.logonUser)
}
});
}
</script>
\ No newline at end of file
const lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const reverseLookup = new Uint8Array(256);
// 初始化 reverseLookup 数组
for (let i = 0; i < lookup.length; i++) {
reverseLookup[lookup.charCodeAt(i)] = i;
}
function decodeBase64url(base64url) {
var base64urlLength = base64url.length;
var placeHolderLength = base64url.charAt(base64urlLength - 2) === '=' ? 2 : base64url.charAt(base64urlLength - 1) === '=' ? 1 : 0;
var bufferLength = (base64urlLength * 3 / 4) - placeHolderLength;
var arrayBuffer = new ArrayBuffer(bufferLength);
var uint8Array = new Uint8Array(arrayBuffer);
var j = 0;
for (var i = 0; i < base64urlLength; i+=4) {
var tmp0 = reverseLookup[base64url.charCodeAt(i)];
var tmp1 = reverseLookup[base64url.charCodeAt(i+1)];
var tmp2 = reverseLookup[base64url.charCodeAt(i+2)];
var tmp3 = reverseLookup[base64url.charCodeAt(i+3)];
uint8Array[j++] = (tmp0 << 2) | (tmp1 >> 4);
uint8Array[j++] = ((tmp1 & 15) << 4) | (tmp2 >> 2);
uint8Array[j++] = ((tmp2 & 3) << 6) | (tmp3 & 63);
}
return arrayBuffer;
}
function encodeBase64url(arrayBuffer) {
var uint8Array = new Uint8Array(arrayBuffer);
var length = uint8Array.length;
var base64url = "";
for (var i = 0; i < length; i+=3) {
base64url += lookup[uint8Array[i] >> 2];
base64url += lookup[((uint8Array[i] & 3) << 4) | (uint8Array[i + 1] >> 4)];
base64url += lookup[((uint8Array[i + 1] & 15) << 2) | (uint8Array[i + 2] >> 6)];
base64url += lookup[uint8Array[i + 2] & 63];
}
switch (length % 3) {
case 1:
base64url = base64url.substring(0, base64url.length - 2);
break;
case 2:
base64url = base64url.substring(0, base64url.length - 1);
break;
}
return base64url;
}
// 导出函数
export default { decodeBase64url, encodeBase64url };
\ No newline at end of file
import { ref, computed } from 'vue';
/**
* 创建一个倒计时的自定义组合函数。
*
* @param {number} duration - 倒计时的初始秒数,默认为60秒。
* @returns {object} 包含倒计时状态和控制方法的对象。
*/
export default function useCountdown(duration = 60) {
// 使用 ref 创建响应式的倒计时剩余时间
const timeLeft = ref(duration);
// 用于存储定时器 ID 的变量
let intervalId = null;
// 计算属性,将剩余时间转换为分钟和秒数
const minutes = computed(() => Math.floor(timeLeft.value / 60));
const seconds = computed(() =>
('0' + (timeLeft.value % 60)).slice(-2) // 确保秒数始终显示两位数字
);
/**
* 开始倒计时。
*/
function start() {
if (!intervalId && timeLeft.value > 0) {
// 设置每秒减少一次剩余时间的定时器
intervalId = setInterval(() => {
if (timeLeft.value <= 0) {
clearInterval(intervalId); // 当时间用完时清除定时器
intervalId = null;
return;
}
timeLeft.value--; // 每秒减少一秒
}, 1000);
}
}
/**
* 停止倒计时。
*/
function stop() {
if (intervalId) {
clearInterval(intervalId); // 清除定时器以停止倒计时
intervalId = null;
}
}
/**
* 重置倒计时到初始值或指定的新持续时间。
*
* @param {number} [newDuration=duration] - 新的倒计时持续时间(秒),可选。
*/
function reset(newDuration = duration) {
stop(); // 先停止当前倒计时
timeLeft.value = newDuration; // 重置剩余时间为新的持续时间或默认持续时间
}
// 返回包含倒计时状态和控制方法的对象
return {
timeLeft, // 剩余时间(秒)
minutes, // 分钟
seconds, // 秒
start, // 开始
stop, // 暂停
reset // 重置
};
}
\ No newline at end of file
import type { FormPlus, FormExpose } from '@/components/FormPlus'
import type { ElForm, ElFormItem } from 'element-plus'
import { ref, unref, nextTick } from 'vue'
import { FormSchema, FormSetProps, FormProps } from '@/components/FormPlus'
import { isEmptyVal, isObject } from '@/utils/common'
import { cloneDeep } from 'lodash-es'
import { ComponentRef } from '@/types/global'
export const useForm = () => {
// From实例
const formRef = ref<typeof FormPlus & FormExpose>()
// ElForm实例
const elFormRef = ref<ComponentRef<typeof ElForm>>()
/**
* @param ref Form实例
* @param elRef ElForm实例
*/
const register = (ref: typeof FormPlus & FormExpose, elRef: ComponentRef<typeof ElForm>) => {
formRef.value = ref
elFormRef.value = elRef
}
const getForm = async () => {
await nextTick()
const form = unref(formRef)
if (!form) {
console.error('The form is not registered. Please use the register method to register')
}
return form
}
/**
* !数据类型空值映射
*/
const defaultEmptyValues = (fieldType:string | undefined):'' | any[] | null => {
let map = {
'string':'',
'number':'',
'array':[],
'date':null
}
return fieldType ? map[fieldType] : ''
}
// 一些内置的方法
const methods = {
/**
* @description 设置form组件的props
* @param props form组件的props
*/
setProps: async (props: FormProps = {}) => {
const form = await getForm()
form?.setProps(props)
if (props.model) {
form?.setValues(props.model)
}
},
/**
* @description 设置form的值
* @param data 需要设置的数据
*/
setValues: async (data: any) => {
const form = await getForm()
form?.setValues(data)
},
/**
* @description 设置schema
* @param schemaProps 需要设置的schemaProps
*/
setSchema: async (schemaProps: FormSetProps[]) => {
const form = await getForm()
form?.setSchema(schemaProps)
},
/**
* @description 新增schema
* @param formSchema 需要新增数据
* @param index 在哪里新增
*/
addSchema: async (formSchema: FormSchema, index?: number) => {
const form = await getForm()
form?.addSchema(formSchema, index)
},
/**
* @description 新增schema
* @param formSchemas 需要新增数据
* @param index 在哪里新增
*/
addSchemas: async (formSchemas: FormSchema[], index?: number) => {
const form = await getForm()
form?.addSchemas(formSchemas, index)
},
/**
* @description 删除schema
* @param field 删除哪个数据
* @param deleteField 是否删除字段
*/
delSchema: async (field: string,deleteField:boolean = true) => {
const form = await getForm()
form?.delSchema(field,deleteField)
},
/**
* @description 获取表单数据
* @returns form data
*/
getFormData: async (filterEmptyVal = true): Promise<any> => {
const form = await getForm()
const model = form?.formModel as any
if (filterEmptyVal) {
// 使用reduce过滤空值,并返回一个新对象
return Object.keys(model).reduce((prev, next) => {
const value = model[next]
if (!isEmptyVal(value)) {
if (isObject(value)) {
if (Object.keys(value).length > 0) {
prev[next] = value
}
} else {
prev[next] = value
}
}
return prev
}, {}) as T
} else {
return model as T
}
},
/**
* @description 获取表单组件的实例
* @param field 表单项唯一标识
* @returns component instance
*/
getComponentExpose: async (field: string) => {
const form = await getForm()
return form?.getComponentExpose(field)
},
/**
* @description 获取formItem组件的实例
* @param field 表单项唯一标识
* @returns formItem instance
*/
getFormItemExpose: async (field: string) => {
const form = await getForm()
return form?.getFormItemExpose(field) as ComponentRef<typeof ElFormItem>
},
/**
* @description 获取ElForm组件的实例
* @returns ElForm instance
*/
getElFormExpose: async () => {
await getForm()
return unref(elFormRef)
},
getFormExpose: async () => {
await getForm()
return unref(formRef)
},
/**
* 根据schema拿到表单的完整数据(如果字段为空则返回空字符串)
* @param resultForm
* @param schemaParam
* @returns
*/
getFormResult: (resultForm, schemaParam: FormSchema[]) => {
let res = {}
schemaParam.forEach(item => {
res[item.field] = isEmptyVal(resultForm[item.field]) ? defaultEmptyValues(item.fieldType) : resultForm[item.field]
})
return res
}
}
return {
formRegister: register,
formMethods: methods
}
}
/**
*
* @param reserveKeys 需要保留的key
* @param obj 需要删除的对象
* @description 删除对象中的key
*/
export const reserveField = (reserveKeys: string[],obj) => {
let copy = cloneDeep(obj)
for (const key in copy) {
if (reserveKeys.indexOf(key) === -1) {
delete copy[key]
}
}
return copy
}
import dayjs from '@/utils/dayjs'
import useUserStore from '@/store/modules/user'
import { getOrganisationRelTreeListPromise, getTemplateListPromise } from "@/api/modules/dataBasic"
const currentDate = dayjs(new Date()).format('YYYY-MM-DD')
const isArray = (val: any): val is Array<any> => {
return val && Array.isArray(val)
}
......@@ -15,81 +11,8 @@ const isNonEmptyArray = (val: any): boolean => {
const useGetData = (param = {}) => {
const BasicInfo: any = ref({}) // 基础资料
const gradeList = ref<any>([]) // 职级关系
const platformGradeList = ref<any>([]) // 平台职级
const postionList = ref<any>([]) // 职位
const tenantRelList = ref<any>([]) // 公司关系
const templateList = ref<any>([]) // 菜单模板列表
const orgMap = ref<orgMapRes>() // 人员组织信息
const organisationTree = ref<any>([]) // 组织树
const personelTree = ref<any>([]) // 人员树
const amoebaTree = ref<any>([]) // 阿米巴树
const financeSubjectDict = ref<any>([]) // 关联财务科目字典
const systemSideList = ref<{ // 系统列表
systemName: string,
guid: string
}[]>([])
const getFinanceSubject = async (customParam = {}) => { // 获取业务线
return await budgetApi.getFinanceSubjectTreePromise((Object.assign({}, param, customParam)))
}
// !基础数据缓存机制
async function getFinanceSubjectTree({ useCache = true, customParam = {} } = {}) {
if (isNonEmptyArray(BasicInfo.financeSubject) && useCache) {
return BasicInfo.financeSubject
} else {
let res = await getFinanceSubject(customParam)
BasicInfo.financeSubject = useCache ? res : []
return res
}
}
async function getGradeTitleRelList({ useCache = true, customParam = {} } = {}) { // 获取职级关系列表
if ( isNonEmptyArray(BasicInfo.gradeList) && useCache) {
gradeList.value = BasicInfo.gradeList
return gradeList.value
} else {
let res = await userApi.getGradeRelListPromise(customParam)
BasicInfo.gradeList = useCache ? res : []
gradeList.value = res
return gradeList.value
}
}
async function getGradeList({ useCache = true, customParam = {} } = {}) { // 获取平台职级列表
if ( isNonEmptyArray(BasicInfo.platformGradeList) && useCache) {
platformGradeList.value = BasicInfo.platformGradeList
return platformGradeList.value
} else {
let res = await userApi.getGradeListPromise(customParam)
BasicInfo.platformGradeList = useCache ? res : []
platformGradeList.value = res
return platformGradeList.value
}
}
async function getPostionList({ useCache = true, customParam = {} } = {}) { // 获取职位
if ( isNonEmptyArray(BasicInfo.postionList) && useCache) {
postionList.value = BasicInfo.postionList
return postionList.value
} else {
let res = await userApi.getPositionPromise(customParam)
BasicInfo.postionList = useCache ? res : []
postionList.value = res
return postionList.value
}
}
async function getTenantRelList(useCache = true) { // 获取公司关系列表
if (isNonEmptyArray(BasicInfo.tenantRelList) && useCache) {
tenantRelList.value = BasicInfo.tenantRelList
return tenantRelList.value
} else {
let res = await tenantApi.getTenantListPromise('Y')
BasicInfo.tenantRelList = useCache ? res : []
tenantRelList.value = res
return tenantRelList.value
}
}
async function getPermissionTemplateList({ useCache = true, customParam = {} } = {}) { // 获取菜单模板
if (isNonEmptyArray(templateList.value) && useCache) {
return templateList.value
......@@ -100,47 +23,6 @@ const useGetData = (param = {}) => {
}
}
interface orgMapRes {
[key:string]:{
guid:string, // staffGuid
userGuid:string, // userGuid
orgGuid:string, // 部门
orgName:string,
orgGuidTop:string, // 一级部门guid
orgNameTop:number,
}
}
// 获取人员组织信息
async function getOrgMap({ useCache = true, customParam = {} } = {}) { // 获取人员组织信息
const userStore = useUserStore()
let staffGuid = userStore.userInfo.staffGuid
if (isObject(BasicInfo.orgMap) && BasicInfo.orgMap[staffGuid] && useCache) {
orgMap.value = BasicInfo.orgMap
return orgMap.value
} else {
const userStore = useUserStore()
let res = await staffApi.getOrgMap([userStore.userInfo.staffGuid])
BasicInfo.orgMap = useCache ? res : null
orgMap.value = res
return orgMap.value
}
}
// 获取组织树
async function getPersonelTree({ useCache = true, customParam = {} } = {}) {
if ( isNonEmptyArray(BasicInfo.personelTree) && useCache) {
personelTree.value = BasicInfo.personelTree
return personelTree.value
} else {
const userStore = useUserStore()
let res = await tenantApi.getOrganisationTreePromise(userStore.userInfo.tenantGuid)
mulTreeData(res)
BasicInfo.personelTree = useCache ? res : []
personelTree.value = res
return personelTree.value
}
}
async function getOrganisationTree({ useCache = true, tenantGuid = '' } = {}) { // 获取组织树
if ( isNonEmptyArray(BasicInfo.organisationTree) && useCache) {
organisationTree.value = BasicInfo.organisationTree
......@@ -154,59 +36,9 @@ const useGetData = (param = {}) => {
}
}
async function getAmoebaTree({ useCache = true, customParam = {} } = {}) { // 获取组织树
if ( isNonEmptyArray(BasicInfo.amoebaTree) && useCache) {
amoebaTree.value = BasicInfo.amoebaTree
return amoebaTree.value
} else {
let res = await tenantApi.getAmoebaTreePromise2(currentDate)
getOrgtreeData(res,false)
BasicInfo.amoebaTree = useCache ? res : []
amoebaTree.value = res
return amoebaTree.value
}
}
async function getFinanceDict({ useCache = true, customParam = {} } = {}) { // 获取财务科目关联字典
if ( isNonEmptyArray(BasicInfo.financeSubjectDict) && useCache) {
financeSubjectDict.value = BasicInfo.financeSubjectDict
return financeSubjectDict.value
} else {
let res = await budgetApi.getSubjectDict(customParam)
getOrgtreeData(res,false)
BasicInfo.financeSubjectDict = useCache ? res : []
financeSubjectDict.value = res
return financeSubjectDict.value
}
}
// 获取子系统列表
async function getSystemSideList({ useCache = true, customParam = {} } = {}) {
if ( isNonEmptyArray(BasicInfo.systemSideList) && useCache) {
systemSideList.value = BasicInfo.systemSideList
return systemSideList.value
} else {
let res = await authApi.getSystemSideData()
BasicInfo.systemSideList = useCache ? res : []
systemSideList.value = res
return systemSideList.value
}
}
return {
getPersonelTree,
getFinanceSubject,
getGradeTitleRelList,
getGradeList,
getPostionList,
getTenantRelList,
getFinanceSubjectTree,
getPermissionTemplateList,
getOrgMap,
getOrganisationTree,
getAmoebaTree,
getFinanceDict,
getSystemSideList
}
}
......
......@@ -11,7 +11,6 @@ import useKeepAliveStore from '@/store/modules/keepAlive'
import useUserStore from '@/store/modules/user'
import useMenuStore from '@/store/modules/menu'
import useRouteStore from '@/store/modules/route'
import route from '@/mock/route'
const { isLoading } = useNProgress()
......@@ -125,19 +124,19 @@ router.beforeEach(async (to, from, next) => {
}
}
else {
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' ||
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' ||
to.name == 'homeDamDemand' || to.name == 'homeDamAlgorithm' || to.name == 'homeDamAlgorithmMobile' || to.name == 'homeDamMarket' || to.name == 'homeDamMarketMobile'
|| to.name == 'homeDamDataCirculeMobile' || to.name == 'homeDamDemandMobile' || to.name == 'homeDamFinanceMobile') {
next()
}
else if (!to.query.code && to.name !== 'login') {
window.location.href = import.meta.env.VITE_IDASS_BASEURL;
// next({
// name: 'login',
// query: {
// redirect: to.fullPath !== '/' ? to.fullPath : undefined,
// },
// })
// window.location.href = import.meta.env.VITE_IDASS_BASEURL;
next({
name: 'login',
query: {
redirect: to.fullPath !== '/' ? to.fullPath : undefined,
},
})
}
else {
next()
......
......@@ -26,14 +26,30 @@ interface metaInfoRaw {
// 固定路由(默认路由)
const constantRoutes: RouteRecordRaw[] = [
// {
// path: '/login',
// name: 'login',
// component: () => import('@/views/login.vue'),
// meta: {
// title: '登录',
// },
// },
{
path: '/portalLogin',
name: 'login',
component: () => import('@/views/portal/portalLogin.vue'),
meta: {
title: '登录',
},
},
{
path: '/userPrivate',
name: 'userPrivate',
component: () => import('@/views/portal/userPrivate.vue'),
meta: {
title: '隐私声明',
},
},
{
path: '/userAgree',
name: 'userAgree',
component: () => import('@/views/portal/userAgree.vue'),
meta: {
title: '用户协议',
},
},
{
path: '/:all(.*)*',
name: 'notFound',
......
import { idaasLogin } from '@/api/modules/idaas';
import { autoSalt } from '@/utils/common';
import { ElMessage } from 'element-plus';
const useIdaas = defineStore(
// 唯一ID
'idaas',
() => {
const idaasToken = ref('') // idaas token
const isLoginOut = ref(false);// idaas 退出登录。
const idaasUserInfo = ref<{
principal?:{
logonUser:string,
mobileNo:string,
name:string
}
}>({})
// 登录
function login(data: any) {
data.username = data.logonUser;
data.password = autoSalt(data.password, false, false);
delete data.userType;
delete data.platformGuid;
delete data.logonUser;
data.needToastErr = 0;
data.telAreaCode = '+86';
isLoginOut.value = false;
return idaasLogin(data).then((res: any) => {
if (res?.code == '00000') {
// ElMessage.success('登录成功');
idaasUserInfo.value = res.data.data
return res.data;
} else {
ElMessage.error(res.msg)
}
})
}
return {
idaasToken,
idaasUserInfo,
login
}
},
{
persist:{
storage: localStorage,
paths: ['idaasToken','idaasUserInfo']
}
}
)
export default useIdaas
import { defineStore } from 'pinia'
const useLogin = defineStore(
// 唯一ID
'login',
() => {
const isCheckSms = ref(false)
const smsValidateCode = ref('');
const firstUnmetRequirement = ref('');
const encodePwd = ref('')
return {
/**
* 密文
*/
encodePwd,
/**
* 是否直接去校验验证码
*/
isCheckSms,
/**
* 网关登录验证码
*/
smsValidateCode,
/**
* 注册密码未满足的第一条规则的 label
*/
firstUnmetRequirement,
}
},
{
persist:{
storage: sessionStorage,
paths: ['firstUnmetRequirement','smsValidateCode','encodePwd']
}
}
)
export default useLogin
\ No newline at end of file
const sysConfigStore = defineStore(
// 唯一ID
'config',
() => {
let configMap: any = {};
// 封装请求配置文件的函数
const loadConfig = async () => {
try {
const response = await fetch('/config.json');
if (!response.ok) {
throw new Error(`请求配置失败,状态码: ${response.status}`);
}
const config = await response.json();
return config;
} catch (error) {
console.error('加载配置时出错:', error);
throw error;
}
};
const setConfig = (val) => {
configMap = val
}
const getConfig = (field) => {
if (import.meta.env.MODE == 'nginx' || import.meta.env.MODE == 'development') {
return import.meta.env.VITE_appKey
}
return field ? configMap[field] : configMap;
}
return {
configMap,
loadConfig,
setConfig,
getConfig
}
},
)
export default sysConfigStore
......@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
Dialog_form: typeof import('./../components/Dialog/dialog_form.vue')['default']
Dialog_grid: typeof import('./../components/Dialog/dialog_grid.vue')['default']
Dialog_pane: typeof import('./../components/Dialog/dialog_pane.vue')['default']
DialogPlus: typeof import('./../components/DialogPlus/src/DialogPlus.vue')['default']
Drawer: typeof import('./../components/Drawer/index.vue')['default']
EchartsMap: typeof import('./../components/EchartsMap/index.vue')['default']
Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
......@@ -28,13 +29,17 @@ declare module '@vue/runtime-core' {
FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default']
Form: typeof import('./../components/Form/index.vue')['default']
FormItem: typeof import('./../components/FormItem/FormItem.vue')['default']
FormPlus: typeof import('./../components/FormPlus/src/FormPlus.vue')['default']
GraphTopbar: typeof import('./../components/RelationNetwork/graphTopbar.vue')['default']
Header: typeof import('./../components/Header/index.vue')['default']
Hour: typeof import('./../components/Schedule/component/hour.vue')['default']
ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default']
ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
LineageGraph: typeof import('./../components/LineageGraph/index.vue')['default']
ListPanel: typeof import('./../components/ListPanel/index.vue')['default']
Logo: typeof import('./../components/Logo/index.vue')['default']
LookBpmn: typeof import('./../components/ApprovalProcess/src/components/LookBpmn.vue')['default']
Month: typeof import('./../components/Schedule/component/month.vue')['default']
NotAllowed: typeof import('./../components/NotAllowed/index.vue')['default']
......@@ -46,6 +51,7 @@ declare module '@vue/runtime-core' {
PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default']
Popover: typeof import('./../components/Popover/index.vue')['default']
RelationNetwork: typeof import('./../components/RelationNetwork/index.vue')['default']
Retrievepassword: typeof import('./../components/Retrievepassword/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Schedule: typeof import('./../components/Schedule/index.vue')['default']
......
......@@ -7,6 +7,11 @@ type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>
}
declare type ComponentRef<T> = InstanceType<T>
declare type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T>
declare namespace Settings {
interface app {
/**
......
......@@ -1255,3 +1255,71 @@ export const isAllPropertiesEmpty = (obj,keyList?: string[]) => {
// 如果所有属性都有值,则返回true
return false;
}
/** 生成state的hash值 */
export const createStateHashCode = () => {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
return array[0].toString(16) + Date.now();
}
/**
* 获取本地图
* @param name // 文件名 如 doc.png
* @returns {*|string}
*/
export const getAssetsImages = (name) => {
return new URL(`../assets/images/${name}`, import.meta.url).href;
}
export const blobToImageLink = (data):Promise<string> => {
return new Promise((resolve, reject) => {
let blob = new Blob([data],{type:'image/png'}); // #识别文件类型
let objectUrl = URL.createObjectURL(blob);
resolve(objectUrl)
})
}
export function isJsonString(str) {
if (typeof str !== 'string') return false;
// 忽略前导和尾随空白字符
str = str.trim();
// 检查字符串是否以 { 或 [ 开始,并以 } 或 ] 结束
if (!str.startsWith('{') && !str.startsWith('[')) return false;
if (!str.endsWith('}') && !str.endsWith(']')) return false;
try {
JSON.parse(str);
return true;
} catch (error) {
return false;
}
}
const toString = Object.prototype.toString
export const is = (val: unknown, type: string) => {
return toString.call(val) === `[object ${type}]`
}
export const isEmptyVal = (val: any): boolean => {
return val === '' || val === null || val === undefined
}
export const isObject = (val: any): val is Record<any, any> => {
return val !== null && is(val, 'Object')
}
/**
* 首字母大写
*/
export function firstUpperCase(str: string) {
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
}
/**
* 驼峰转横杠
*/
export const humpToDash = (str: string): string => {
return str.replace(/([A-Z])/g, '-$1').toLowerCase()
}
......
import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
import { CSSProperties } from 'vue'
type PropTypes = VueTypesInterface & {
readonly style: VueTypeValidableDef<CSSProperties>
}
const newPropTypes = createTypes({
func: undefined,
bool: undefined,
string: undefined,
number: undefined,
object: undefined,
integer: undefined
}) as PropTypes
class propTypes extends newPropTypes {
static get style() {
return toValidableType('style', {
type: [String, Object]
})
}
}
export { propTypes }
import axios, {CancelTokenSource} from "axios";
import Storage from './composables/storage-helper';
import CryptoHelper from './composables/cryptoJs-helper';
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
......@@ -13,9 +12,10 @@ interface Request {
source: CancelTokenSource;
}
const pendingRequests: Request[] = [];
const storage = new Storage();
const cryptoHelper = new CryptoHelper('cacheKey');
const IDaaSBaseURL = 'idaas'
const service = axios.create({
baseURL: (import.meta.env.VITE_OPEN_PROXY === 'true') ? `/api/` : `${import.meta.env.VITE_API_BASEURL}`,
timeout: 10 * 60 * 1000,// request timeout
......@@ -52,9 +52,15 @@ service.interceptors.request.use(
config.headers['real-ip'] = localStorage.getItem('ipAddress');
return config;
}
if (config.method === "idaasPost" || config.method === "idaaspost") {
config.baseURL = IDaaSBaseURL;
config.method = "post";
} else if (config.method === "idaasGet" || config.method === "idaasget") {
config.baseURL = IDaaSBaseURL;
config.method = "get";
}
if (config.responseType == "blob") {
// 文件流,文件名称相同时会判定同一个请求。
const userStore = useUserStore();
config.headers.Authorization = localStorage.getItem('token');
config.headers['real-ip'] = localStorage.getItem('ipAddress');
return config;
......
<script lang="ts" setup name="Header">
import Logo from '../Logo/index.vue';
import useSettingsStore from '@/store/modules/settings';
const { proxy } = getCurrentInstance() as any;
const settingsStore = useSettingsStore();
const currentPath = computed(() => proxy.$route.path)
onMounted(()=>{
})
</script>
<template>
<transition name="header">
<header v-if="settingsStore.mode === 'pc' && settingsStore.settings.menu.menuMode === 'head'">
<div class="header-container">
<Logo />
<!-- <Tools v-if="currentPath !== '/portalLogin'" /> -->
</div>
</header>
</transition>
</template>
<style lang="scss" scoped>
header {
// position: fixed;
// z-index: 1000;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
height: 64px;
color: var(--g-header-color);
// background: url('@/assets/images/header_bg.png') center/100% 100% no-repeat;
background-color: #fff;
transition: background-color 0.3s, var(--el-transition-color);
background-size: cover;
// position: relative;
.header-container {
width: var(--g-header-width);
height: 100%;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.sidebar-collapse {
position: absolute;
left: calc(var(--g-sub-sidebar-width-portal) + 10px);
top: 50%;
transform: translateY(-50%);
z-index: 2;
cursor: pointer;
:deep(.el-icon) {
color: #fff;
}
}
.main {
width: calc(var(--g-header-width) - var(--g-sub-sidebar-width-portal) - 48px);
height: 100%;
padding-right: 24px;
}
@media screen and (max-width: var(--g-header-width)) {
.header-container {
width: 100%;
}
}
:deep(.title) {
position: relative;
width: calc(var(--g-sub-sidebar-width-portal) + 18px);
height: inherit;
padding-left: 18px;
padding-right: 0;
background-color: inherit;
flex-shrink: 0;
span {
font-size: 24px;
letter-spacing: 1px;
color: var(--g-header-color);
}
}
.nav {
display: flex;
width: 100%;
height: 100%;
padding: 0 25px;
align-items: center;
overflow-x: auto;
mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
// firefox隐藏滚动条
scrollbar-width: none;
// chrome隐藏滚动条
&::-webkit-scrollbar {
display: none;
}
.item-container {
position: relative;
display: flex;
width: initial;
height: inherit;
.item {
padding: 0 12px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-width: 60px;
height: 100%;
cursor: pointer;
color: var(--g-header-menu-color);
background-color: transparent;
// transition: background-color 0.3s, var(--el-transition-color);
&:hover {
color: var(--g-header-menu-hover-color);
background-color: var(--g-header-menu-hover-bg);
}
.el-icon {
font-size: 24px;
vertical-align: middle;
}
span {
text-align: center;
vertical-align: middle;
word-break: break-all;
@include text-overflow(1, false);
}
}
&.active .item {
color: var(--g-header-menu-active-color);
background-color: var(--g-header-menu-active-bg);
}
}
}
:deep(.tools) {
.buttons .item .el-icon {
color: var(--g-header-color);
}
.user-container {
font-size: 16px;
color: var(--g-header-color);
outline: none !important;
}
}
}
// 头部动画
.header-enter-active,
.header-leave-active {
transition: transform 0.3s;
}
.header-enter-from,
.header-leave-to {
transform: translateY(calc(var(--g-header-height-portal) * -1));
}
</style>
<script lang="ts" setup name="Logo">
import imgLogo from '@/assets/images/数据资产运营平台logo.png'
import useSettingsStore from '@/store/modules/settings'
defineProps({
showLogo: {
type: Boolean,
default: true,
},
showTitle: {
type: Boolean,
default: true,
},
})
const settingsStore = useSettingsStore()
const title = ref(import.meta.env.VITE_APP_TITLE)
const logo = ref(imgLogo)
const to = computed(() => {
const rtn: {
name?: string
} = {}
if (settingsStore.settings.home.enable) {
rtn.name = 'portal'
}
return rtn
})
</script>
<template>
<div class="title">
<img v-if="showLogo" :src="logo" class="logo">
<!-- <router-link :to="to" :class="{ 'is-link': settingsStore.settings.home.enable }" :title="title">
<img v-if="showLogo" :src="logo" class="logo">
</router-link>
<el-divider direction="vertical" /> -->
</div>
</template>
<style lang="scss" scoped>
.title {
position: fixed;
z-index: 1000;
top: 0;
width: inherit;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
height: var(--g-sidebar-logo-height);
text-align: center;
overflow: hidden;
text-decoration: none;
&.is-link {
cursor: pointer;
}
.logo {
width: 264px;
height: 64px;
object-fit: contain;
&+span {
margin-left: 10px;
}
}
span {
display: block;
font-weight: bold;
color: #fff;
@include text-overflow;
}
.el-divider {
height: 1.5rem;
margin-right: 0;
}
}
</style>
......@@ -35,11 +35,11 @@ export default ({ mode, command }) => {
changeOrigin: env.VITE_OPEN_PROXY === 'true',
rewrite: path => path.replace(/\/api/, ''),
},
'/portal':{
target: env.VITE_API_PORTALURL,
changeOrigin: env.VITE_OPEN_PROXY === 'true',
rewrite: path => path.replace(/\/portal/, ''),
},
// '/portal':{
// target: env.VITE_API_PORTALURL,
// changeOrigin: env.VITE_OPEN_PROXY === 'true',
// rewrite: path => path.replace(/\/portal/, ''),
// },
'/circulation':{
target: env.VITE_APP_CIRCULATION,
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!