93 changed files with 4515 additions and 267 deletions
@ -0,0 +1,7 @@ |
|||||
|
dependencies { |
||||
|
api( |
||||
|
project(":io.sc.platform.mvc"), |
||||
|
|
||||
|
"org.springframework.boot:spring-boot-starter-mail", |
||||
|
) |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
package io.sc.platform.communication.service; |
||||
|
|
||||
|
import javax.mail.MessagingException; |
||||
|
import javax.mail.internet.MimeMessage; |
||||
|
|
||||
|
import org.springframework.mail.SimpleMailMessage; |
||||
|
|
||||
|
/** |
||||
|
* 邮件发送器服务 |
||||
|
*/ |
||||
|
public interface MailSenderService { |
||||
|
/** |
||||
|
* 发送简单邮件 |
||||
|
* @param from 发件人 |
||||
|
* @param to 收件人 |
||||
|
* @param cc 抄送人 |
||||
|
* @param subject 标题 |
||||
|
* @param text 文本 |
||||
|
*/ |
||||
|
public void sendSimpleMessage(String from,String[] to,String[] cc,String subject,String text); |
||||
|
|
||||
|
/** |
||||
|
* 发送简单邮件 |
||||
|
* @param messages 邮件消息 |
||||
|
*/ |
||||
|
public void sendSimpleMessage(SimpleMailMessage... messages); |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 发送富文本邮件 |
||||
|
* @param from 发件人 |
||||
|
* @param to 收件人 |
||||
|
* @param cc 抄送人 |
||||
|
* @param subject 标题 |
||||
|
* @param text 文本 |
||||
|
* @throws MessagingException 违例 |
||||
|
*/ |
||||
|
public void sendMimeMessage(String from,String[] to,String[] cc,String subject,String text) throws MessagingException; |
||||
|
|
||||
|
/** |
||||
|
* 发送富文本邮件 |
||||
|
* @param mimeMessages 邮件消息 |
||||
|
*/ |
||||
|
public void sendMimeMessage(MimeMessage... mimeMessages); |
||||
|
} |
@ -0,0 +1,66 @@ |
|||||
|
package io.sc.platform.communication.service.impl; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.mail.MessagingException; |
||||
|
import javax.mail.internet.MimeMessage; |
||||
|
|
||||
|
import io.sc.platform.communication.service.MailSenderService; |
||||
|
import io.sc.platform.core.util.BeanUtil; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.context.ApplicationContext; |
||||
|
import org.springframework.mail.SimpleMailMessage; |
||||
|
import org.springframework.mail.javamail.JavaMailSender; |
||||
|
import org.springframework.mail.javamail.MimeMessageHelper; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
@Service |
||||
|
public class MailSenderServiceImpl implements MailSenderService { |
||||
|
@Autowired private ApplicationContext applicationContext; |
||||
|
private JavaMailSender mailSender; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void initMailSenderService(){ |
||||
|
this.mailSender = BeanUtil.getBean(applicationContext,JavaMailSender.class); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendSimpleMessage(String from, String[] to, String[] cc, String subject, String text) { |
||||
|
if(mailSender!=null){ |
||||
|
SimpleMailMessage message = new SimpleMailMessage(); |
||||
|
message.setFrom(from); |
||||
|
message.setTo(to); |
||||
|
message.setCc(cc); |
||||
|
message.setSubject(subject); |
||||
|
message.setText(text); |
||||
|
sendSimpleMessage(message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendSimpleMessage(SimpleMailMessage... messages) { |
||||
|
if(mailSender!=null){ |
||||
|
mailSender.send(messages); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendMimeMessage(String from, String[] to, String[] cc, String subject, String html) throws MessagingException { |
||||
|
if(mailSender!=null) { |
||||
|
MimeMessage message = mailSender.createMimeMessage(); |
||||
|
MimeMessageHelper helper = new MimeMessageHelper(message, true); |
||||
|
helper.setFrom(from); |
||||
|
helper.setTo(to); |
||||
|
helper.setCc(cc); |
||||
|
helper.setSubject(subject); |
||||
|
helper.setText(html, true); |
||||
|
sendMimeMessage(message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendMimeMessage(MimeMessage... mimeMessages) { |
||||
|
if(mailSender!=null) { |
||||
|
mailSender.send(mimeMessages); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
[ |
||||
|
{ |
||||
|
"module" : "io.sc.platform.communication", |
||||
|
"order" : 4000, |
||||
|
"description": "email configuration", |
||||
|
"properties": [ |
||||
|
"spring.mail.host=zzz.xxx.yyy", |
||||
|
"spring.mail.port=25", |
||||
|
"spring.mail.protocol=smtp", |
||||
|
"spring.mail.test-connection=false", |
||||
|
"spring.mail.default-encoding=UTF-8", |
||||
|
"spring.mail.properties.mail.smtp.auth=true", |
||||
|
"spring.mail.username=xxx", |
||||
|
"spring.mail.password=yyy" |
||||
|
] |
||||
|
} |
||||
|
] |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"includes":[ |
||||
|
"io.sc.platform.communication.service.impl" |
||||
|
] |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
{ |
||||
|
"container":[ |
||||
|
"spring.mail.host", |
||||
|
"spring.mail.port", |
||||
|
"spring.mail.protocol", |
||||
|
"spring.mail.test-connection", |
||||
|
"spring.mail.default-encoding", |
||||
|
"spring.mail.properties.mail.smtp.auth", |
||||
|
"spring.mail.username", |
||||
|
"spring.mail.password" |
||||
|
], |
||||
|
"jar": [ |
||||
|
"spring.mail.host", |
||||
|
"spring.mail.port", |
||||
|
"spring.mail.protocol", |
||||
|
"spring.mail.test-connection", |
||||
|
"spring.mail.default-encoding", |
||||
|
"spring.mail.properties.mail.smtp.auth", |
||||
|
"spring.mail.username", |
||||
|
"spring.mail.password" |
||||
|
] |
||||
|
} |
@ -1,10 +1,5 @@ |
|||||
<template> |
<template> |
||||
<DndProvider :backend="HTML5Backend"> |
<w-platform-page></w-platform-page> |
||||
<w-platform-page></w-platform-page> |
|
||||
</DndProvider> |
|
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"></script> |
||||
import { DndProvider } from 'vue3-dnd'; |
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'; |
|
||||
</script> |
|
||||
|
@ -0,0 +1,118 @@ |
|||||
|
<template> |
||||
|
<q-dialog v-model="dialog.dialogShow" allow-focus-outside v-bind="extractDialogProps(dialogProps)" :maximized="dialog.maximizedToggle"> |
||||
|
<q-card :style="dialogStyleComputed"> |
||||
|
<div class="w-full h-full"> |
||||
|
<div style="height: 69px"> |
||||
|
<q-card-section> |
||||
|
<div class="flex justify-between"> |
||||
|
<div class="text-h6">{{ dialogTitle }}</div> |
||||
|
<div class="flex justify-end gap-4"> |
||||
|
<template v-for="(btn, index) in dialogButtons as any" :key="index"> |
||||
|
<q-btn |
||||
|
v-if="typeof btn === 'object'" |
||||
|
:loading="btn.loading ? btn.loading : false" |
||||
|
:label="btn.label" |
||||
|
:icon="btn.icon" |
||||
|
color="primary" |
||||
|
@click="btn.click()" |
||||
|
> |
||||
|
</q-btn> |
||||
|
</template> |
||||
|
<q-btn |
||||
|
v-if="dialogMaximized" |
||||
|
dense |
||||
|
flat |
||||
|
:icon="!dialog.maximizedToggle ? PlatformIconEnum.全屏 : PlatformIconEnum.退出全屏" |
||||
|
@click="dialogMaximizeBtnClick" |
||||
|
> |
||||
|
<q-tooltip v-if="!dialog.maximizedToggle">全屏</q-tooltip> |
||||
|
<q-tooltip v-else-if="dialog.maximizedToggle">退出全屏</q-tooltip> |
||||
|
</q-btn> |
||||
|
<q-btn v-close-popup dense flat :icon="PlatformIconEnum.关闭"> |
||||
|
<q-tooltip>关闭</q-tooltip> |
||||
|
</q-btn> |
||||
|
</div> |
||||
|
</div> |
||||
|
</q-card-section> |
||||
|
<q-separator /> |
||||
|
</div> |
||||
|
<div style="height: calc(100% - 69px)"> |
||||
|
<q-card-section v-if="!dialogSplitter" style="height: 100%" class="scroll"> |
||||
|
<slot name="content"></slot> |
||||
|
</q-card-section> |
||||
|
<q-splitter v-else v-model="dialog.splitterModel" :limits="dialog.splitterLimits" :horizontal="dialog.splitterHorizontal" style="height: 100%"> |
||||
|
<template #before> |
||||
|
<slot name="splitterBefore"></slot> |
||||
|
</template> |
||||
|
<template #after> |
||||
|
<slot name="splitterAfter"></slot> |
||||
|
</template> |
||||
|
</q-splitter> |
||||
|
</div> |
||||
|
</div> |
||||
|
</q-card> |
||||
|
</q-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { reactive, computed } from 'vue'; |
||||
|
import { extractDialogProps, PlatformIconEnum } from '@/platform/components/utils'; |
||||
|
|
||||
|
import '@/css/tailwind.css'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
dialogProps: { |
||||
|
type: Object, |
||||
|
default: () => { |
||||
|
return {}; |
||||
|
}, |
||||
|
}, |
||||
|
dialogTitle: { type: String, default: '' }, |
||||
|
dialogInitWidth: { type: String, default: '70%' }, |
||||
|
dialogInitHeight: { type: String, default: '70%' }, |
||||
|
dialogInitMaximized: { type: Boolean, default: false }, |
||||
|
dialogMaximized: { type: Boolean, default: true }, |
||||
|
dialogButtons: { type: Array, default: () => [] }, |
||||
|
dialogSplitter: { type: Object, default: undefined }, |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
( |
||||
|
e: 'dialogMaximize', // 全屏按钮点击事件 |
||||
|
maximizedToggle: boolean, // 第一个参数,true:全屏状态,false:退出全屏 |
||||
|
): void; |
||||
|
}>(); |
||||
|
|
||||
|
const dialog = reactive({ |
||||
|
dialogShow: false, |
||||
|
maximizedToggle: props.dialogInitMaximized, |
||||
|
splitterModel: props.dialogSplitter?.splitterModel || 50, |
||||
|
splitterLimits: props.dialogSplitter?.limits || [0, 100], |
||||
|
splitterHorizontal: props.dialogSplitter?.horizontal || false, |
||||
|
}); |
||||
|
|
||||
|
const dialogMaximizeBtnClick = () => { |
||||
|
dialog.maximizedToggle = !dialog.maximizedToggle; |
||||
|
emit('dialogMaximize', dialog.maximizedToggle); |
||||
|
}; |
||||
|
|
||||
|
const dialogStyleComputed = computed(() => { |
||||
|
if (!dialog.maximizedToggle) { |
||||
|
return { width: props.dialogInitWidth, 'max-width': '100vw', height: props.dialogInitHeight, 'max-height': '100vh' }; |
||||
|
} else { |
||||
|
return { width: '100vw', 'max-width': '100vw', height: '100vh', 'max-height': '100vh' }; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const dialogShow = () => { |
||||
|
dialog.dialogShow = true; |
||||
|
}; |
||||
|
const dialogHide = () => { |
||||
|
dialog.dialogShow = false; |
||||
|
}; |
||||
|
|
||||
|
defineExpose({ |
||||
|
dialogShow, |
||||
|
dialogHide, |
||||
|
}); |
||||
|
</script> |
@ -0,0 +1,107 @@ |
|||||
|
<template> |
||||
|
<q-dialog v-model="dialog.dialogShow" allow-focus-outside v-bind="attrs"> |
||||
|
<q-card |
||||
|
:style="{ |
||||
|
width: dialog.maximized ? '100vw' : props.width, |
||||
|
'max-width': '100vw', |
||||
|
height: dialog.maximized ? '100vh' : props.height, |
||||
|
'max-height': '100vh', |
||||
|
}" |
||||
|
> |
||||
|
<div class="w-full h-full"> |
||||
|
<div style="height: 69px"> |
||||
|
<q-card-section> |
||||
|
<div class="flex justify-between"> |
||||
|
<div class="text-h6">{{ title }}</div> |
||||
|
<div class="flex justify-end gap-4"> |
||||
|
<template v-for="(btn, index) in buttons as any" :key="index"> |
||||
|
<q-btn |
||||
|
v-if="typeof btn === 'object'" |
||||
|
:loading="btn.loading ? btn.loading : false" |
||||
|
:label="btn.label" |
||||
|
:icon="btn.icon" |
||||
|
color="primary" |
||||
|
@click="btn.click()" |
||||
|
> |
||||
|
</q-btn> |
||||
|
</template> |
||||
|
<q-btn v-if="canMaximize" dense flat :icon="!dialog.maximized ? IconEnum.全屏 : IconEnum.退出全屏" @click="maximizeBtnClick"> |
||||
|
<q-tooltip v-if="!dialog.maximized">全屏</q-tooltip> |
||||
|
<q-tooltip v-else-if="dialog.maximized">退出全屏</q-tooltip> |
||||
|
</q-btn> |
||||
|
<q-btn v-close-popup dense flat :icon="IconEnum.关闭"> |
||||
|
<q-tooltip>关闭</q-tooltip> |
||||
|
</q-btn> |
||||
|
</div> |
||||
|
</div> |
||||
|
</q-card-section> |
||||
|
<q-separator /> |
||||
|
</div> |
||||
|
<div style="height: calc(100% - 69px)"> |
||||
|
<q-card-section v-if="!splitter" style="height: 100%" class="scroll"> |
||||
|
<slot name="content"></slot> |
||||
|
</q-card-section> |
||||
|
<q-splitter v-else v-model="dialog.splitterModel" :limits="dialog.splitterLimits" :horizontal="dialog.splitterHorizontal" style="height: 100%"> |
||||
|
<template #before> |
||||
|
<slot name="splitterBefore"></slot> |
||||
|
</template> |
||||
|
<template #after> |
||||
|
<slot name="splitterAfter"></slot> |
||||
|
</template> |
||||
|
</q-splitter> |
||||
|
</div> |
||||
|
</div> |
||||
|
</q-card> |
||||
|
</q-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { reactive, useAttrs } from 'vue'; |
||||
|
import { IconEnum } from '@/platform/enums'; |
||||
|
import { Tools } from '@/platform/utils'; |
||||
|
|
||||
|
import '@/css/tailwind.css'; |
||||
|
|
||||
|
const attrs = useAttrs(); |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
title: { type: String, default: '' }, |
||||
|
width: { type: String, default: '70%' }, |
||||
|
height: { type: String, default: '70%' }, |
||||
|
canMaximize: { type: Boolean, default: true }, |
||||
|
buttons: { type: Array, default: () => [] }, |
||||
|
splitter: { type: Object, default: undefined }, |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
( |
||||
|
e: 'maximized', // 全屏按钮点击事件 |
||||
|
maximized: boolean, // 第一个参数,true:全屏状态,false:退出全屏 |
||||
|
): void; |
||||
|
}>(); |
||||
|
|
||||
|
const dialog = reactive({ |
||||
|
show: false, |
||||
|
maximized: attrs.maximized, |
||||
|
splitterModel: props.splitter?.splitterModel || 50, |
||||
|
splitterLimits: props.splitter?.limits || [0, 100], |
||||
|
splitterHorizontal: props.splitter?.horizontal || false, |
||||
|
}); |
||||
|
|
||||
|
const maximizeBtnClick = () => { |
||||
|
dialog.maximized = !dialog.maximized; |
||||
|
emit('maximized', dialog.maximized); |
||||
|
}; |
||||
|
|
||||
|
const show = () => { |
||||
|
dialog.dialogShow = true; |
||||
|
}; |
||||
|
const hide = () => { |
||||
|
dialog.dialogShow = false; |
||||
|
}; |
||||
|
|
||||
|
defineExpose({ |
||||
|
show, |
||||
|
hide, |
||||
|
}); |
||||
|
</script> |
@ -0,0 +1,103 @@ |
|||||
|
<template> |
||||
|
<q-dialog v-model="drawer.drawerShow" v-bind="extractDialogProps(drawerProps)" :maximized="drawer.maximizedToggle"> |
||||
|
<q-card :style="drawerStyleComputed"> |
||||
|
<div class="w-full h-full"> |
||||
|
<div style="height: 64px"> |
||||
|
<q-card-section> |
||||
|
<div class="flex justify-between"> |
||||
|
<div class="text-h6">{{ drawerTitle }}</div> |
||||
|
<div class="flex justify-end gap-4"> |
||||
|
<q-btn |
||||
|
v-if="drawerMaximized" |
||||
|
dense |
||||
|
flat |
||||
|
:icon="!drawer.actualMaximizedToggle ? PlatformIconEnum.全屏 : PlatformIconEnum.退出全屏" |
||||
|
@click="drawerMaximizeBtnClick" |
||||
|
> |
||||
|
<q-tooltip v-if="!drawer.actualMaximizedToggle">全屏</q-tooltip> |
||||
|
<q-tooltip v-else-if="drawer.actualMaximizedToggle">退出全屏</q-tooltip> |
||||
|
</q-btn> |
||||
|
<q-btn v-close-popup dense flat :icon="PlatformIconEnum.关闭"> |
||||
|
<q-tooltip>关闭</q-tooltip> |
||||
|
</q-btn> |
||||
|
</div> |
||||
|
</div> |
||||
|
</q-card-section> |
||||
|
<q-separator /> |
||||
|
</div> |
||||
|
<div :style="drawerButtons && drawerButtons.length > 0 ? 'height:calc(100% - 132px)' : 'height:calc(100% - 64px)'"> |
||||
|
<q-card-section style="height: 100%" class="scroll"> |
||||
|
<slot name="content"></slot> |
||||
|
</q-card-section> |
||||
|
</div> |
||||
|
<div v-if="drawerButtons && drawerButtons.length > 0" style="height: 64px"> |
||||
|
<q-separator /> |
||||
|
<q-card-actions align="right" class="px-6 pt-4"> |
||||
|
<template v-for="(btn, index) in drawerButtons as any" :key="index"> |
||||
|
<q-btn |
||||
|
v-if="typeof btn === 'object'" |
||||
|
:loading="btn.loading ? btn.loading : false" |
||||
|
:label="btn.label" |
||||
|
:icon="btn.icon" |
||||
|
unelevated |
||||
|
outline |
||||
|
@click="btn.click()" |
||||
|
></q-btn> |
||||
|
</template> |
||||
|
</q-card-actions> |
||||
|
</div> |
||||
|
</div> |
||||
|
</q-card> |
||||
|
</q-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { reactive, computed } from 'vue'; |
||||
|
import { extractDialogProps, PlatformIconEnum } from '@/platform/components/utils'; |
||||
|
|
||||
|
import '@/css/tailwind.css'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
drawerProps: { type: Object, default: () => {} }, |
||||
|
drawerTitle: { type: String, default: '' }, |
||||
|
drawerInitWidth: { type: String, default: '50vw' }, |
||||
|
drawerInitHeight: { type: String, default: '100vh' }, |
||||
|
drawerMaximized: { type: Boolean, default: true }, |
||||
|
drawerButtons: { type: Array, default: () => [] }, |
||||
|
}); |
||||
|
|
||||
|
const drawer = reactive({ |
||||
|
drawerShow: false, |
||||
|
maximizedToggle: true, |
||||
|
/** |
||||
|
* 实际全屏属性,Drawer 使用的 dialog 进行封装,而 dialog 的非全屏模式, |
||||
|
* 不管高宽给到多少,Quasar 内置的 css 样式都会有一个 padding: 24px 的属性, |
||||
|
* 所以封装 Drawer 时默认就是 dialog 的全屏模式,同时希望提供全屏的功能,所以需要一个实际是否全屏的属性。 |
||||
|
*/ |
||||
|
actualMaximizedToggle: false, |
||||
|
}); |
||||
|
|
||||
|
const drawerMaximizeBtnClick = () => { |
||||
|
drawer.actualMaximizedToggle = !drawer.actualMaximizedToggle; |
||||
|
}; |
||||
|
|
||||
|
const drawerStyleComputed = computed(() => { |
||||
|
if (!drawer.actualMaximizedToggle) { |
||||
|
return { width: props.drawerInitWidth, 'max-width': '100vw', height: props.drawerInitHeight, 'max-height': '100vh' }; |
||||
|
} else { |
||||
|
return { width: '100vw', 'max-width': '100vw', height: '100vh', 'max-height': '100vh' }; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const drawerShow = () => { |
||||
|
drawer.drawerShow = true; |
||||
|
}; |
||||
|
const drawerHide = () => { |
||||
|
drawer.drawerShow = false; |
||||
|
}; |
||||
|
|
||||
|
defineExpose({ |
||||
|
drawerShow, |
||||
|
drawerHide, |
||||
|
}); |
||||
|
</script> |
@ -0,0 +1,528 @@ |
|||||
|
<template> |
||||
|
<q-form ref="formRef" v-bind="extractFormProps(formProps)"> |
||||
|
<div class="grid gap-2" :class="formLayoutComputed"> |
||||
|
<template v-for="(field, index) in formFields as any" :key="String(index)"> |
||||
|
<q-input |
||||
|
v-if="field.type === 'dateRange' && index < queryFormShowFieldNumber" |
||||
|
v-show="!field.hasOwnProperty('hide') || !field.hide" |
||||
|
v-model="formData[field.fmtModelName]" |
||||
|
:label="field.required ? '* ' + field.label : field.label" |
||||
|
:rules="fieldRulesFun(field.required, field)" |
||||
|
v-bind="extractFormItemComponentProps(field.type, field)" |
||||
|
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
||||
|
@update:model-value="field.changeFun" |
||||
|
> |
||||
|
<template #append> |
||||
|
<q-icon :name="PlatformIconEnum.日期范围" class="cursor-pointer"> |
||||
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> |
||||
|
<q-date |
||||
|
v-model="formData[field.modelName]" |
||||
|
range |
||||
|
:mask="field.mask ? field.mask : 'YYYY-MM-DD'" |
||||
|
:readonly="formStatus === 'view' || field.readonly ? true : false" |
||||
|
> |
||||
|
<div class="row items-center justify-end"> |
||||
|
<q-btn v-close-popup label="关闭" color="primary" flat /> |
||||
|
</div> |
||||
|
</q-date> |
||||
|
</q-popup-proxy> |
||||
|
</q-icon> |
||||
|
</template> |
||||
|
</q-input> |
||||
|
<q-input |
||||
|
v-else-if="field.type === 'date' && index < queryFormShowFieldNumber" |
||||
|
v-show="!field.hasOwnProperty('hide') || !field.hide" |
||||
|
v-model="formData[field.modelName]" |
||||
|
:label="field.required ? '* ' + field.label : field.label" |
||||
|
:rules="fieldRulesFun(field.required, field)" |
||||
|
v-bind="extractFormItemComponentProps(field.type, field)" |
||||
|
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
||||
|
@update:model-value="field.changeFun" |
||||
|
> |
||||
|
<template #append> |
||||
|
<q-icon :name="PlatformIconEnum.日期" class="cursor-pointer"> |
||||
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> |
||||
|
<q-date |
||||
|
v-model="formData[field.modelName]" |
||||
|
today-btn |
||||
|
:mask="field.mask ? field.mask : 'YYYY-MM-DD'" |
||||
|
:readonly="formStatus === 'view' || field.readonly ? true : false" |
||||
|
> |
||||
|
<div class="row items-center justify-end"> |
||||
|
<q-btn v-close-popup label="关闭" color="primary" flat /> |
||||
|
</div> |
||||
|
</q-date> |
||||
|
</q-popup-proxy> |
||||
|
</q-icon> |
||||
|
</template> |
||||
|
</q-input> |
||||
|
<q-select |
||||
|
v-else-if="field.type === 'select' && index < queryFormShowFieldNumber" |
||||
|
v-show="!field.hasOwnProperty('hide') || !field.hide" |
||||
|
v-model="formData[field.modelName]" |
||||
|
:label="field.required ? '* ' + field.label : field.label" |
||||
|
:rules="fieldRulesFun(field.required, field)" |
||||
|
v-bind="extractFormItemComponentProps(field.type, field)" |
||||
|
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
||||
|
@update:model-value="field.changeFun" |
||||
|
@filter="field.filterFun" |
||||
|
@focus="field.focusFun" |
||||
|
> |
||||
|
<template v-if="field.afterButton" #after> |
||||
|
<q-btn |
||||
|
:round="field.afterButton.round" |
||||
|
:dense="field.afterButton.dense" |
||||
|
:flat="field.afterButton.flat" |
||||
|
:unelevated="field.afterButton.unelevated" |
||||
|
:outline="field.afterButton.outline" |
||||
|
:color="field.afterButton.color" |
||||
|
:icon="field.afterButton.icon" |
||||
|
:label="field.afterButton.label" |
||||
|
:disable="formStatus === PageStatusEnum.查看 || field.afterButton.disable ? true : false" |
||||
|
@click="field.afterButton.click" |
||||
|
/> |
||||
|
</template> |
||||
|
</q-select> |
||||
|
<q-checkbox |
||||
|
v-else-if="field.type === 'checkbox' && index < queryFormShowFieldNumber" |
||||
|
v-show="!field.hasOwnProperty('hide') || !field.hide" |
||||
|
v-model="formData[field.modelName]" |
||||
|
:label="field.required ? '* ' + field.label : field.label" |
||||
|
v-bind="extractFormItemComponentProps(field.type, field)" |
||||
|
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
||||
|
@update:model-value="field.changeFun" |
||||
|
/> |
||||
|
<div |
||||
|
v-else-if="field.type === 'optionGroup' && index < queryFormShowFieldNumber" |
||||
|
v-show="!field.hasOwnProperty('hide') || !field.hide" |
||||
|
class="border-solid border" |
||||
|
> |
||||
|
<span class="p-2.5">{{ field.label }}</span> |
||||
|
<q-option-group |
||||
|
v-model="formData[field.modelName]" |
||||
|
v-bind="extractFormItemComponentProps(field.type, field)" |
||||
|
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
||||
|
@update:model-value="field.changeFun" |
||||
|
/> |
||||
|
</div> |
||||
|
<q-input |
||||
|
v-else-if="index < queryFormShowFieldNumber" |
||||
|
v-show="!field.hasOwnProperty('hide') || !field.hide" |
||||
|
v-model="formData[field.modelName]" |
||||
|
:label="field.required ? '* ' + field.label : field.label" |
||||
|
:rules="fieldRulesFun(field.required, field)" |
||||
|
v-bind="extractFormItemComponentProps(field.type, field)" |
||||
|
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
||||
|
@update:model-value="field.changeFun" |
||||
|
> |
||||
|
<template v-if="field.afterButton" #after> |
||||
|
<q-btn |
||||
|
round |
||||
|
dense |
||||
|
flat |
||||
|
:disable="formStatus === PageStatusEnum.查看 || field.afterButton.disable ? true : false" |
||||
|
:icon="field.afterButton.icon" |
||||
|
@click="field.afterButton.click" |
||||
|
/> |
||||
|
</template> |
||||
|
</q-input> |
||||
|
</template> |
||||
|
</div> |
||||
|
<slot></slot> |
||||
|
</q-form> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, watch, computed, toRaw, defineProps } from 'vue'; |
||||
|
import { |
||||
|
extractFormProps, |
||||
|
extractFormItemComponentProps, |
||||
|
arrayToMap, |
||||
|
PlatformIconEnum, |
||||
|
FormComponentValidateEnum, |
||||
|
PageStatusEnum, |
||||
|
isEmpty, |
||||
|
} from '@/platform/components/utils'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
formProps: { |
||||
|
type: Object, |
||||
|
default: () => { |
||||
|
return { autofocus: false, greedy: true }; |
||||
|
}, |
||||
|
}, |
||||
|
formColsNumber: { type: Number, default: 3 }, |
||||
|
formColsAuto: { type: Boolean, default: true }, |
||||
|
formFields: { |
||||
|
type: Array, |
||||
|
default: () => { |
||||
|
return []; |
||||
|
}, |
||||
|
}, |
||||
|
queryFormShowFieldNumber: { type: Number, default: 999 }, |
||||
|
}); |
||||
|
|
||||
|
const formRef = ref(); |
||||
|
const formStatus = ref('add'); |
||||
|
const formFieldsMap = arrayToMap('modelName', props.formFields); |
||||
|
|
||||
|
// 由于采用字符串拼接 class 名称,tailwind 无法生效,模板文件中必须出现完整的类名,最终构建的css文件中才会包含进去。 |
||||
|
const formLayoutComputed = computed(() => { |
||||
|
let className = ''; |
||||
|
switch (props.formColsNumber) { |
||||
|
case 1: |
||||
|
className = !props.formColsAuto ? 'grid-cols-1' : 'xs:grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'; |
||||
|
break; |
||||
|
case 2: |
||||
|
className = !props.formColsAuto ? 'grid-cols-2' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'; |
||||
|
break; |
||||
|
case 4: |
||||
|
className = !props.formColsAuto ? 'grid-cols-4' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-6'; |
||||
|
break; |
||||
|
case 5: |
||||
|
className = !props.formColsAuto ? 'grid-cols-5' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6'; |
||||
|
break; |
||||
|
case 6: |
||||
|
className = !props.formColsAuto ? 'grid-cols-6' : 'xs:grid-cols-1 sm:grid-cols-3 md:grid-cols-6 lg:grid-cols-6 xl:grid-cols-6'; |
||||
|
break; |
||||
|
case 7: |
||||
|
className = !props.formColsAuto ? 'grid-cols-7' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-7 lg:grid-cols-7 xl:grid-cols-7'; |
||||
|
break; |
||||
|
case 8: |
||||
|
className = !props.formColsAuto ? 'grid-cols-8' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-8 lg:grid-cols-8 xl:grid-cols-8'; |
||||
|
break; |
||||
|
case 9: |
||||
|
className = !props.formColsAuto ? 'grid-cols-9' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-9 lg:grid-cols-9 xl:grid-cols-9'; |
||||
|
break; |
||||
|
case 10: |
||||
|
className = !props.formColsAuto ? 'grid-cols-10' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-10 lg:grid-cols-10 xl:grid-cols-10'; |
||||
|
break; |
||||
|
case 11: |
||||
|
className = !props.formColsAuto ? 'grid-cols-11' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-11 lg:grid-cols-11 xl:grid-cols-11'; |
||||
|
break; |
||||
|
case 12: |
||||
|
className = !props.formColsAuto ? 'grid-cols-12' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-12 lg:grid-cols-12 xl:grid-cols-12'; |
||||
|
break; |
||||
|
default: |
||||
|
className = !props.formColsAuto ? 'grid-cols-3' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'; |
||||
|
} |
||||
|
return className; |
||||
|
}); |
||||
|
|
||||
|
const selectDefaultValue = (field) => { |
||||
|
if (field.hasOwnProperty.call('defaultValue') && field.defaultValue !== null) { |
||||
|
if (typeof field.defaultValue === 'string' || typeof field.defaultValue === 'boolean' || typeof field.defaultValue === 'number') { |
||||
|
const dftValue = field.options.filter((item) => { |
||||
|
return item.value === field.defaultValue; |
||||
|
}); |
||||
|
if (dftValue && dftValue.length > 0) { |
||||
|
return field.multiple ? [dftValue[0]] : dftValue[0]; |
||||
|
} else { |
||||
|
return field.multiple ? [] : ''; |
||||
|
} |
||||
|
} else if (Array.isArray(field.defaultValue)) { |
||||
|
const dftValue = field.options.filter((item) => { |
||||
|
return field.defaultValue.filter((val) => { |
||||
|
return val === item.value; |
||||
|
}); |
||||
|
}); |
||||
|
return field.multiple ? dftValue : dftValue[0]; |
||||
|
} |
||||
|
} else { |
||||
|
return field.multiple ? [] : ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const numberDefaultValue = (field) => { |
||||
|
if (field.hasOwnProperty.call('defaultValue') && typeof field.defaultValue === 'number') { |
||||
|
return field.defaultValue; |
||||
|
} else { |
||||
|
return ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const checkboxDefaultValue = (field) => { |
||||
|
if (field.hasOwnProperty.call('defaultValue')) { |
||||
|
return field.defaultValue; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const optionGroupDefaultValue = (field) => { |
||||
|
if (field.hasOwnProperty.call('defaultValue')) { |
||||
|
return field.defaultValue; |
||||
|
} else if (field.optionGroupType && field.optionGroupType === 'checkbox') { |
||||
|
return []; |
||||
|
} else { |
||||
|
return ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const textDefaultValue = (field) => { |
||||
|
if (field.hasOwnProperty.call('defaultValue')) { |
||||
|
return field.defaultValue; |
||||
|
} else { |
||||
|
return ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const formModel: any = {}; |
||||
|
for (const field of props.formFields as any) { |
||||
|
if (field.type === 'dateRange') { |
||||
|
formModel[field.fmtModelName] = ''; |
||||
|
} else if (field.type === 'select') { |
||||
|
formModel[field.modelName] = selectDefaultValue(field); |
||||
|
} else if (field.type === 'number') { |
||||
|
formModel[field.modelName] = numberDefaultValue(field); |
||||
|
} else if (field.type === 'checkbox') { |
||||
|
formModel[field.modelName] = checkboxDefaultValue(field); |
||||
|
} else if (field.type === 'optionGroup') { |
||||
|
formModel[field.modelName] = optionGroupDefaultValue(field); |
||||
|
} else { |
||||
|
formModel[field.modelName] = textDefaultValue(field); |
||||
|
} |
||||
|
} |
||||
|
const formData = reactive(formModel); |
||||
|
for (const field of props.formFields as any) { |
||||
|
if (field.type === 'dateRange') { |
||||
|
watch( |
||||
|
() => formData[field.modelName], |
||||
|
(newVal, oldVal) => { |
||||
|
if (!newVal || newVal.from === '') { |
||||
|
formData[field.fmtModelName] = ''; |
||||
|
} else { |
||||
|
formData[field.fmtModelName] = newVal.from + ' 至 ' + newVal.to; |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const isNumber = (num) => { |
||||
|
return !isNaN(parseFloat(num)) && isFinite(num); |
||||
|
}; |
||||
|
|
||||
|
const fieldRulesFun = (required, field) => { |
||||
|
let resultRules = <any>[]; |
||||
|
if (field.type === 'select' && field.multiple && required) { |
||||
|
resultRules.push((val) => { |
||||
|
if (val !== null && val.length > 0) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '该字段为必填项'; |
||||
|
} |
||||
|
}); |
||||
|
} else if (required) { |
||||
|
resultRules.push((val) => (val !== null && val !== '') || '该字段为必填项'); |
||||
|
} |
||||
|
if (field.rules && field.rules.length > 0) { |
||||
|
field.rules.forEach((rule) => { |
||||
|
if (typeof rule === 'string' && rule === FormComponentValidateEnum.字符串不能包含空格校验) { |
||||
|
// 字符串不能包含空格校验 |
||||
|
resultRules.push((val) => { |
||||
|
if (isEmpty(val) || val.indexOf(' ') === -1) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '不能包含空格'; |
||||
|
} |
||||
|
}); |
||||
|
} else if (typeof rule === 'string' && rule === FormComponentValidateEnum.默认日期格式校验) { |
||||
|
resultRules.push((val) => { |
||||
|
if (isEmpty(val) || /^(\d{4})-(\d{2})-(\d{2})$/.test(val)) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '日期格式校验未通过'; |
||||
|
} |
||||
|
}); |
||||
|
} else if (typeof rule === 'string' && rule === FormComponentValidateEnum.必须为整数校验) { |
||||
|
// 必须为整数校验 |
||||
|
resultRules.push((val) => { |
||||
|
const tmp = String(val); |
||||
|
if (val === null || (tmp.indexOf('.') === -1 && Number.isInteger(parseInt(tmp)))) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '只能输入整数'; |
||||
|
} |
||||
|
}); |
||||
|
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.字符串最大长度校验) { |
||||
|
// 字符串最大长度校验 |
||||
|
resultRules.push((val) => { |
||||
|
const tmp = String(val); |
||||
|
if (val === null || tmp.length <= rule.value) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '最大允许输入的长度为:' + rule.value; |
||||
|
} |
||||
|
}); |
||||
|
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.最大小数位数校验) { |
||||
|
// 最大小数位数校验 |
||||
|
resultRules.push((val) => { |
||||
|
const tmp = String(val); |
||||
|
if (val === null || tmp.indexOf('.') === -1 || tmp.substring(tmp.indexOf('.') + 1).length <= rule.value) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '最大允许输入的小数位数为:' + rule.value; |
||||
|
} |
||||
|
}); |
||||
|
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.数字最小值校验) { |
||||
|
// 数字最小值校验 |
||||
|
resultRules.push((val) => { |
||||
|
const tmp = String(val); |
||||
|
if (val === null || parseFloat(tmp) >= rule.value) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '最小允许输入的值为:' + rule.value; |
||||
|
} |
||||
|
}); |
||||
|
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.数字最大值校验) { |
||||
|
// 数字最大值校验 |
||||
|
resultRules.push((val) => { |
||||
|
const tmp = String(val); |
||||
|
if (val === null || parseFloat(tmp) <= rule.value) { |
||||
|
return true; |
||||
|
} else { |
||||
|
return '最大允许输入的值为:' + rule.value; |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
resultRules.push(rule); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
return resultRules; |
||||
|
}; |
||||
|
|
||||
|
const formValidateFun = async () => { |
||||
|
const v = await formValidate(); |
||||
|
return v; |
||||
|
}; |
||||
|
const formValidate = async () => { |
||||
|
let validate = false; |
||||
|
await formRef.value.validate().then((success) => { |
||||
|
if (success) { |
||||
|
validate = true; |
||||
|
} |
||||
|
}); |
||||
|
return validate; |
||||
|
}; |
||||
|
|
||||
|
const getFormDataFun = () => { |
||||
|
const data = { ...toRaw(formData) }; |
||||
|
const selectFields = props.formFields.filter((item: any) => { |
||||
|
return item.type === 'select'; |
||||
|
}); |
||||
|
selectFields.forEach((item: any) => { |
||||
|
let selectValues = ''; |
||||
|
if (item.multiple && data[item.modelName] && data[item.modelName].length > 0) { |
||||
|
data[item.modelName].forEach((val) => { |
||||
|
selectValues = selectValues + ',' + val.value; |
||||
|
}); |
||||
|
selectValues = selectValues.substring(1, selectValues.length); |
||||
|
} else if (data[item.modelName]) { |
||||
|
selectValues = data[item.modelName].value ?? data[item.modelName]; |
||||
|
} |
||||
|
data[item.modelName] = selectValues; |
||||
|
}); |
||||
|
return data; |
||||
|
}; |
||||
|
|
||||
|
const setFormDataFun = (record) => { |
||||
|
for (const field of props.formFields as any) { |
||||
|
if (field.type === 'select') { |
||||
|
if (field.multiple && record[field.modelName].indexOf(',') > -1) { |
||||
|
const recordSelectValues = record[field.modelName].split(','); |
||||
|
const selectValues = <any>[]; |
||||
|
recordSelectValues.forEach((item) => { |
||||
|
selectValues.push(setSelectValue(field.options, item)); |
||||
|
}); |
||||
|
formData[field.modelName] = selectValues; |
||||
|
} else { |
||||
|
formData[field.modelName] = setSelectValue(field.options, record[field.modelName]); |
||||
|
} |
||||
|
} else if (field.type === 'optionGroup') { |
||||
|
if (record[field.modelName]) { |
||||
|
formData[field.modelName] = record[field.modelName]; |
||||
|
} else { |
||||
|
formData[field.modelName] = []; |
||||
|
} |
||||
|
} else { |
||||
|
formData[field.modelName] = record[field.modelName]; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const setSelectValue = (options, val) => { |
||||
|
const opt = options.filter((option) => { |
||||
|
return option.value === val; |
||||
|
}); |
||||
|
if (opt && opt.length > 0) { |
||||
|
return opt[0]; |
||||
|
} else { |
||||
|
return val; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const setFieldValueFun = (fieldName, value) => { |
||||
|
const field = formFieldsMap.get(fieldName); |
||||
|
if (field.type === 'select') { |
||||
|
if (field.multiple && Array.isArray(value)) { |
||||
|
const selectValues = <any>[]; |
||||
|
value.forEach((item) => { |
||||
|
selectValues.push(setSelectValue(field.options, item)); |
||||
|
}); |
||||
|
formData[field.modelName] = selectValues; |
||||
|
} else { |
||||
|
formData[field.modelName] = setSelectValue(field.options, value); |
||||
|
} |
||||
|
} else { |
||||
|
formData[field.modelName] = value; |
||||
|
} |
||||
|
}; |
||||
|
const getFieldValueFun = (fieldName) => { |
||||
|
return formData[fieldName]; |
||||
|
}; |
||||
|
|
||||
|
const resetFormDataFun = () => { |
||||
|
Object.keys(formData).forEach((key) => { |
||||
|
switch (typeof formData[key]) { |
||||
|
case 'string': |
||||
|
formData[key] = ''; |
||||
|
break; |
||||
|
case 'boolean': |
||||
|
formData[key] = false; |
||||
|
break; |
||||
|
case 'number': |
||||
|
formData[key] = ''; |
||||
|
break; |
||||
|
default: |
||||
|
formData[key] = undefined; |
||||
|
break; |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const setFormStatusFun = (status) => { |
||||
|
formStatus.value = status; |
||||
|
}; |
||||
|
|
||||
|
const getFormStatusFun = () => { |
||||
|
return toRaw(formStatus.value); |
||||
|
}; |
||||
|
|
||||
|
const getFormFieldMapFun = () => { |
||||
|
return formFieldsMap; |
||||
|
}; |
||||
|
|
||||
|
defineExpose({ |
||||
|
formValidateFun, |
||||
|
getFormDataFun, |
||||
|
setFormDataFun, |
||||
|
getFormStatusFun, |
||||
|
setFormStatusFun, |
||||
|
resetFormDataFun, |
||||
|
setFieldValueFun, |
||||
|
getFieldValueFun, |
||||
|
getFormFieldMapFun, |
||||
|
}); |
||||
|
</script> |
File diff suppressed because it is too large
@ -0,0 +1,30 @@ |
|||||
|
<template> |
||||
|
<div :ref="(node) => drag(drop(node as any))" :class="borderClass"> |
||||
|
<q-icon v-if="typeof tdValue[0] === 'boolean' && tdValue[0]" :name="PlatformIconEnum.是状态" color="green" size="sm"> </q-icon> |
||||
|
<template v-else-if="typeof tdValue[0] === 'boolean' && !tdValue[0]"> </template> |
||||
|
<template v-else> |
||||
|
{{ tdValue[0] }} |
||||
|
</template> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, unref } from 'vue'; |
||||
|
import { toRefs } from '@vueuse/core'; |
||||
|
import { PlatformIconEnum } from '@/platform/components/utils'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
tdValue: { |
||||
|
type: Array, |
||||
|
default: () => { |
||||
|
return []; |
||||
|
}, |
||||
|
}, |
||||
|
rowIndex: { type: Number, default: 0 }, |
||||
|
}); |
||||
|
const emit = defineEmits(['tableSortFun']); |
||||
|
|
||||
|
export interface DropResult { |
||||
|
name: string; |
||||
|
} |
||||
|
</script> |
@ -0,0 +1,85 @@ |
|||||
|
<template> |
||||
|
<q-markup-table separator="cell" flat bordered wrap-cells> |
||||
|
<tbody> |
||||
|
<template v-for="(trItem, trIndex) in tableComputed" :key="trIndex"> |
||||
|
<tr class="q-tr--no-hover"> |
||||
|
<template v-for="(tdItem, tdIndex) in trItem as any" :key="tdIndex"> |
||||
|
<td :class="labelAlignClassComputed">{{ tdItem.label }}</td> |
||||
|
<td :class="valueAlignClassComputed">{{ tdItem.value }}</td> |
||||
|
</template> |
||||
|
</tr> |
||||
|
</template> |
||||
|
</tbody> |
||||
|
</q-markup-table> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { computed } from 'vue'; |
||||
|
import { getCssVar } from 'quasar'; |
||||
|
import { Environment } from '@/platform'; |
||||
|
|
||||
|
const gc = Environment.getConfigure(); |
||||
|
const darkBgColor = getCssVar('dark'); |
||||
|
const bgColor = gc.theme.dark ? darkBgColor : gc.theme?.grid?.headBgColor || '#f5f7fa'; |
||||
|
const props = defineProps({ |
||||
|
column: { type: Number, default: 1 }, |
||||
|
labelAlign: { type: String, default: 'left' }, |
||||
|
valueAlign: { type: String, default: 'left' }, |
||||
|
infoArray: { |
||||
|
type: Array, |
||||
|
default: () => { |
||||
|
return []; |
||||
|
}, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const tableComputed = computed(() => { |
||||
|
const table = <any>[]; |
||||
|
let tmp = <any>[]; |
||||
|
props.infoArray.forEach((item, index) => { |
||||
|
if (tmp.length < props.column) { |
||||
|
tmp.push(item); |
||||
|
} else { |
||||
|
table.push(tmp); |
||||
|
tmp = []; |
||||
|
tmp.push(item); |
||||
|
} |
||||
|
}); |
||||
|
table.push(tmp); |
||||
|
return table; |
||||
|
}); |
||||
|
const labelAlignClassComputed = computed(() => { |
||||
|
let className = ''; |
||||
|
switch (props.labelAlign) { |
||||
|
case 'right': |
||||
|
className = 'text-right'; |
||||
|
break; |
||||
|
case 'center': |
||||
|
className = 'text-center'; |
||||
|
break; |
||||
|
default: |
||||
|
className = 'text-left'; |
||||
|
} |
||||
|
return className; |
||||
|
}); |
||||
|
const valueAlignClassComputed = computed(() => { |
||||
|
let className = ''; |
||||
|
switch (props.valueAlign) { |
||||
|
case 'right': |
||||
|
className = 'text-right'; |
||||
|
break; |
||||
|
case 'center': |
||||
|
className = 'text-center'; |
||||
|
break; |
||||
|
default: |
||||
|
className = 'text-left'; |
||||
|
} |
||||
|
return className; |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.q-table td:nth-child(odd) { |
||||
|
background-color: v-bind(bgColor); |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,186 @@ |
|||||
|
import { QVueGlobals } from 'quasar'; |
||||
|
|
||||
|
/** |
||||
|
* 平台icon,使用【material icons】,以下为列举的一些平台常用的图标,并不包含全部 |
||||
|
* 不在该枚举中时可使用自定义的图标,目的是以后方便全局替换。 |
||||
|
*/ |
||||
|
export enum PlatformIconEnum { |
||||
|
查询 = 'search', |
||||
|
更多查询 = 'zoom_in', |
||||
|
重置 = 'restart_alt', |
||||
|
刷新 = 'loop', |
||||
|
新增 = 'add', |
||||
|
新增2 = 'add_box', |
||||
|
新增3 = 'playlist_add_circle', |
||||
|
编辑 = 'edit', |
||||
|
删除 = 'delete', |
||||
|
查看 = 'visibility', |
||||
|
全屏 = 'fullscreen', |
||||
|
退出全屏 = 'fullscreen_exit', |
||||
|
关闭 = 'close', |
||||
|
保存 = 'save', |
||||
|
提交 = 'beenhere', |
||||
|
字母 = 'abc', |
||||
|
时钟 = 'access_time', |
||||
|
上箭头 = 'arrow_upward', |
||||
|
下箭头 = 'arrow_downward', |
||||
|
左箭头 = 'arrow_back', |
||||
|
右箭头 = 'arrow_forward', |
||||
|
附件 = 'attach_file', |
||||
|
否状态 = 'cancel', |
||||
|
是状态 = 'check_circle', |
||||
|
首页 = 'home', |
||||
|
设置 = 'settings', |
||||
|
设置2 = 'settings_applications', |
||||
|
扳手 = 'build_circle', |
||||
|
收起 = 'arrow_drop_up', |
||||
|
展开 = 'arrow_drop_down', |
||||
|
提示 = 'info', |
||||
|
警告 = 'warning', |
||||
|
日期 = 'event', |
||||
|
日期范围 = 'date_range', |
||||
|
文件夹 = 'folder', |
||||
|
校验 = 'published_with_changes', |
||||
|
下载 = 'download', |
||||
|
上传 = 'upload', |
||||
|
选取变量 = 'find_in_page', |
||||
|
报表通用 = 'assessment', |
||||
|
复制 = 'file_copy', |
||||
|
发送 = 'send', |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 页面状态(包含:新增=add、编辑=edit、查看=view) |
||||
|
*/ |
||||
|
export enum PageStatusEnum { |
||||
|
新增 = 'add', |
||||
|
编辑 = 'edit', |
||||
|
查看 = 'view', |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Form元素类型枚举 |
||||
|
*/ |
||||
|
export enum FormTypeEnum { |
||||
|
文本框 = 'text', |
||||
|
文本域 = 'textarea', |
||||
|
下拉框 = 'select', |
||||
|
多选下拉框 = 'selectMultiple', |
||||
|
数字框 = 'number', |
||||
|
日期 = 'date', |
||||
|
日期时间 = 'dateTime', |
||||
|
日期范围 = 'dateRange', |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 数据类型枚举 |
||||
|
*/ |
||||
|
export enum DataTypeEnum { |
||||
|
字符串 = 'String', |
||||
|
整数 = 'Integer', |
||||
|
小数 = 'BigDecimal', |
||||
|
日期 = 'Date', |
||||
|
布尔 = 'Boolean', |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下拉框选项值来源枚举 |
||||
|
*/ |
||||
|
export enum OptionComeFromEnum { |
||||
|
数据字典 = 'dictionary', |
||||
|
Java接口 = 'javaApi', |
||||
|
自定义数组 = 'array', |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 平台Form组件内置校验name枚举 |
||||
|
*/ |
||||
|
export enum FormComponentValidateEnum { |
||||
|
字符串不能包含空格校验 = 'noSpace', |
||||
|
必须为整数校验 = 'integer', |
||||
|
默认日期格式校验 = 'date', |
||||
|
字符串最大长度校验 = 'maxLength', |
||||
|
最大小数位数校验 = 'maxPrecision', |
||||
|
数字最小值校验 = 'minValue', |
||||
|
数字最大值校验 = 'maxValue', |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询操作类型枚举 |
||||
|
*/ |
||||
|
export enum OperatorTypeEnum { |
||||
|
isBlank = 'isBlank', // value is null or value=''
|
||||
|
notBlank = 'notBlank', // value is not null && value<>''
|
||||
|
|
||||
|
isNull = 'isNull', // value is null
|
||||
|
notNull = 'notNull', // value is not null
|
||||
|
|
||||
|
equals = 'equals', // =
|
||||
|
notEqual = 'notEqual', // <>
|
||||
|
|
||||
|
greaterThan = 'greaterThan', // >
|
||||
|
greaterOrEqual = 'greaterOrEqual', // >=
|
||||
|
|
||||
|
lessThan = 'lessThan', // <
|
||||
|
lessOrEqual = 'lessOrEqual', // <=
|
||||
|
|
||||
|
contains = 'contains', // like %xxx%
|
||||
|
notContains = 'notContains', // not like %xxx%
|
||||
|
|
||||
|
startsWith = 'startsWith', // like xxx%
|
||||
|
notStartsWith = 'notStartsWith', // not like xxx%
|
||||
|
|
||||
|
endsWith = 'endsWith', // like %xxx
|
||||
|
notEndsWith = 'notEndsWith', // not like %xxx
|
||||
|
|
||||
|
between = 'between', // min<x and x<max
|
||||
|
betweenInclusive = 'betweenInclusive', // min<=x and x<=max
|
||||
|
|
||||
|
inSet = 'inSet', // in ()
|
||||
|
notInSet = 'notInSet', // not in ()
|
||||
|
} |
||||
|
|
||||
|
// 查询对象
|
||||
|
export type CriteriaType = { |
||||
|
fieldName: string; |
||||
|
operator: OperatorTypeEnum; |
||||
|
value: any; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* 平台消息提示类型枚举 |
||||
|
*/ |
||||
|
export enum PlatformNotifyTypeEnum { |
||||
|
成功 = 'positive', |
||||
|
错误 = 'negative', |
||||
|
警告 = 'warning', |
||||
|
信息 = 'info', |
||||
|
灰色消息 = 'ongoing', |
||||
|
} |
||||
|
/** |
||||
|
* 平台消息提示 |
||||
|
* @param message 提示消息 |
||||
|
* @param type PlatformNotifyTypeEnum 枚举提供值,默认 PlatformNotifyTypeEnum.警告 |
||||
|
*/ |
||||
|
export function platformNotify($q: QVueGlobals, message: string, type?: PlatformNotifyTypeEnum) { |
||||
|
$q.notify({ |
||||
|
message: message, |
||||
|
position: 'top', |
||||
|
type: type ?? PlatformNotifyTypeEnum.警告, |
||||
|
actions: [{ label: '知道了', handler: () => {} }], |
||||
|
timeout: 3000, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为空(一般用于Form元素) |
||||
|
* @param obj 需要判断的对象 |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function isEmpty(obj) { |
||||
|
if (typeof obj === 'undefined' || obj === null || obj === '') { |
||||
|
return true; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
@ -0,0 +1,332 @@ |
|||||
|
import { Tools } from '@/platform/utils'; |
||||
|
/** |
||||
|
* 抽取窗口组件属性 |
||||
|
* @param {Object} props 属性对象 |
||||
|
* @returns 窗口组件属性 |
||||
|
*/ |
||||
|
export function extractDialogProps(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.persistent = props.persistent; // 设置后,用户在对话框外单击或按 ESC 键时不再关闭对话框;此外,应用程序路由更改也不会关闭它
|
||||
|
result.noEscDismiss = props.noEscDismiss; // 用户不能按 ESC 键关闭对话框;如果还设置了 'persistent' 属性,则无需设置它
|
||||
|
result.noBackdropDismiss = props.noBackdropDismiss; // 用户不能通过单击对话框外部来关闭对话框;如果还设置了 'persistent' 属性,则无需设置它
|
||||
|
result.noRouteDismiss = props.noRouteDismiss; // 更改路由应用程序不会关闭对话框;如果还设置了 'persistent' 属性,则无需设置它
|
||||
|
result.autoClose = props.autoClose; // 对话框内的任何单击/点击都将关闭它
|
||||
|
result.noShake = props.noShake; // 不要晃动对话框来引起用户的注意。
|
||||
|
result.allowFocusOutside = props.allowFocusOutside; // 允许对话框外的元素可聚焦;出于辅助功能的原因,默认情况下 QDialog 不允许外部聚焦.
|
||||
|
result.seamless = props.seamless; // 使对话框进入无缝模式;不使用背景,因此用户也可以与页面的其他部分进行交互
|
||||
|
result.position = props.position; // 将对话框附着到一侧(默认:standard、top、right、bottom、left)
|
||||
|
result.square = props.square; // 强制内容具有方形边框
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 抽取form表单组件属性 |
||||
|
* @param {Object} props 属性对象 |
||||
|
* @returns 表单组件属性 |
||||
|
*/ |
||||
|
export function extractFormProps(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.autofocus = props.autofocus; // 在初始组件渲染时将第一个可聚焦元素聚焦
|
||||
|
result.greedy = props.greedy; // 验证表单中的所有字段(默认情况下,它在通过同步的验证找到第一个无效字段后停止)
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 抽取表单项组件属性 |
||||
|
* @param {String} type 组件类型 |
||||
|
* @param {Object} props 属性对象 |
||||
|
* @returns 表单项组件属性 |
||||
|
*/ |
||||
|
export function extractFormItemComponentProps(type: string, props: any) { |
||||
|
if ('dateRange' === type) { |
||||
|
return dateRange(props); |
||||
|
} else if ('date' === type) { |
||||
|
return date(props); |
||||
|
} else if ('select' === type) { |
||||
|
return select(props); |
||||
|
} else if ('checkbox' === type) { |
||||
|
return checkbox(props); |
||||
|
} else if ('optionGroup' === type) { |
||||
|
return optionGroup(props); |
||||
|
} else { |
||||
|
return input(props); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* input组件 |
||||
|
* @param props |
||||
|
* @returns |
||||
|
*/ |
||||
|
function input(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.hint = props.hint; // 辅助(提示)文本,放在组件下面
|
||||
|
result.hideBottomSpace = props.hideBottomSpace ?? true; |
||||
|
result.hideHint = props.hideHint ?? true; // 当字段没有焦点时隐藏辅助(提示)文本
|
||||
|
result.stackLabel = props.stackLabel; // 标签将始终显示在字段上方,而不考虑字段内容(如果有)
|
||||
|
result.prefix = props.prefix; // 前缀
|
||||
|
result.suffix = props.suffix; // 后缀
|
||||
|
result.clearable = props.clearable; // 设置值(非 undefined 或 null )时附加可清除图标;单击时,模型将变为空
|
||||
|
result.counter = props.counter; // 在右下角显示自动计数器(字符数)
|
||||
|
result.autogrow = props.autogrow; // 使字段及其内容自动增长(内容过长时组件变高,内容换行)
|
||||
|
result.maxlength = props.maxlength; // 指定模型的最大长度
|
||||
|
result.disable = props.disable; // 将组件置于禁用模式
|
||||
|
result.labelColor = props.labelColor; // 组件 label 的文字颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.color = props.color; // 组件颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.bgColor = props.bgColor; // 组件背景颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.filled = props.filled; // 对字段使用“填充”设计
|
||||
|
result.outlined = props.outlined ?? true; // 对字段使用“轮廓线”设计
|
||||
|
result.borderless = props.borderless; // 对字段采用“无边界”设计,与 outlined 冲突
|
||||
|
result.rounded = props.rounded; // 为组件应用较小标准的边框圆角,也就是边框为椭圆
|
||||
|
result.dense = props.dense ?? true; // 紧凑模式,占用更少的空间
|
||||
|
result.type = props.type; // 组件类型
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 日期范围组件 |
||||
|
* @param props |
||||
|
* @returns |
||||
|
*/ |
||||
|
function dateRange(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.hint = props.hint; // 辅助(提示)文本,放在组件下面
|
||||
|
result.hideHint = props.hideHint ?? true; // 当字段没有焦点时隐藏辅助(提示)文本
|
||||
|
result.hideBottomSpace = props.hideBottomSpace ?? true; |
||||
|
result.stackLabel = props.stackLabel; // 标签将始终显示在字段上方,而不考虑字段内容(如果有)
|
||||
|
result.clearable = props.clearable; // 设置值(非 undefined 或 null )时附加可清除图标;单击时,模型将变为空
|
||||
|
result.disable = props.disable; // 将组件置于禁用模式
|
||||
|
result.labelColor = props.labelColor; // 组件 label 的文字颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.color = props.color; // 组件颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.bgColor = props.bgColor; // 组件背景颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.filled = props.filled; // 对字段使用“填充”设计
|
||||
|
result.outlined = props.outlined ?? true; // 对字段使用“轮廓线”设计
|
||||
|
result.borderless = props.borderless; // 对字段采用“无边界”设计,与 outlined 冲突
|
||||
|
result.rounded = props.rounded; // 为组件应用较小标准的边框圆角,也就是边框为椭圆
|
||||
|
result.dense = props.dense ?? true; // 紧凑模式,占用更少的空间
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 日期组件 |
||||
|
* @param props |
||||
|
* @returns |
||||
|
*/ |
||||
|
function date(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.hint = props.hint; // 辅助(提示)文本,放在组件下面
|
||||
|
result.hideHint = props.hideHint ?? true; // 当字段没有焦点时隐藏辅助(提示)文本
|
||||
|
result.hideBottomSpace = props.hideBottomSpace ?? true; |
||||
|
result.stackLabel = props.stackLabel; // 标签将始终显示在字段上方,而不考虑字段内容(如果有)
|
||||
|
result.clearable = props.clearable; // 设置值(非 undefined 或 null )时附加可清除图标;单击时,模型将变为空
|
||||
|
result.disable = props.disable; // 将组件置于禁用模式
|
||||
|
result.labelColor = props.labelColor; // 组件 label 的文字颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.color = props.color; // 组件颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.bgColor = props.bgColor; // 组件背景颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.filled = props.filled; // 对字段使用“填充”设计
|
||||
|
result.outlined = props.outlined ?? true; // 对字段使用“轮廓线”设计
|
||||
|
result.borderless = props.borderless; // 对字段采用“无边界”设计,与 outlined 冲突
|
||||
|
result.rounded = props.rounded; // 为组件应用较小标准的边框圆角,也就是边框为椭圆
|
||||
|
result.dense = props.dense ?? true; // 紧凑模式,占用更少的空间
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下拉框组件 |
||||
|
* @param props |
||||
|
* @returns |
||||
|
*/ |
||||
|
function select(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.hint = props.hint; // 辅助(提示)文本,放在组件下面
|
||||
|
result.hideHint = props.hideHint ?? true; // 当字段没有焦点时隐藏辅助(提示)文本
|
||||
|
result.hideBottomSpace = props.hideBottomSpace ?? true; |
||||
|
result.stackLabel = props.stackLabel; // 标签将始终显示在字段上方,而不考虑字段内容(如果有)
|
||||
|
result.prefix = props.prefix; // 前缀
|
||||
|
result.suffix = props.suffix; // 后缀
|
||||
|
result.clearable = props.clearable; // 设置值(非 undefined 或 null )时附加可清除图标;单击时,模型将变为空
|
||||
|
result.counter = props.counter; // 在右下角显示自动计数器(字符数)
|
||||
|
result.useInput = props.useInput; // 使用一个输入标签,用户可以在其中输入
|
||||
|
result.autogrow = props.autogrow; // 使字段及其内容自动增长(内容过长时组件变高,内容换行)
|
||||
|
result.disable = props.disable; // 将组件置于禁用模式
|
||||
|
result.labelColor = props.labelColor; // 组件 label 的文字颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.color = props.color; // 组件颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.bgColor = props.bgColor; // 组件背景颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.filled = props.filled; // 对字段使用“填充”设计
|
||||
|
result.outlined = props.outlined ?? true; // 对字段使用“轮廓线”设计
|
||||
|
result.borderless = props.borderless; // 对字段采用“无边界”设计,与 outlined 冲突
|
||||
|
result.rounded = props.rounded; // 为组件应用较小标准的边框圆角,也就是边框为椭圆
|
||||
|
result.dense = props.dense ?? true; // 紧凑模式,占用更少的空间
|
||||
|
result.multiple = props.multiple; // 支持多选
|
||||
|
result.options = props.options; // 下拉选项集合
|
||||
|
result.maxValues = props.maxValues; // 允许用户可以进行的最大选择数
|
||||
|
result.useChips = props.useChips; // 使用QChip显示当前选择的内容
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 复选框组件 |
||||
|
* @param props |
||||
|
* @returns |
||||
|
*/ |
||||
|
function checkbox(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.keepColor = props.keepColor; // 当组件未勾选/关闭时,是否应保留颜色?
|
||||
|
result.checkedIcon = props.checkedIcon; // 此图标将会在 model 值为 true 时被使用(代替默认的设计)
|
||||
|
result.uncheckedIcon = props.uncheckedIcon; // 此图标将会在 model 值为 false 时被使用(代替默认的设计)
|
||||
|
result.toggleIndeterminate = props.toggleIndeterminate ?? false; // 当用户点击组件时,除 true 和 false 外,是否还添加一个不确定(indeterminate)的状态?
|
||||
|
result.leftLabel = props.leftLabel; // 如有标签,应显示在组件的左侧
|
||||
|
result.trueValue = props.trueValue; // model 为何值时被视为选中/勾选/启用?
|
||||
|
result.falseValue = props.falseValue; // model 为何值时被视为未选中/未勾选/关闭?
|
||||
|
result.disable = props.disable; // 将组件置于禁用模式
|
||||
|
result.size = props.size; // 带有 CSS 单位的尺寸大小,包括单位的名称或标准大小名称(xs | sm | md | lg | xl)
|
||||
|
result.color = props.color; // 组件的颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.dense = props.dense; // 紧凑模式,占用更少的空间
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 选项组组件 |
||||
|
* @param props |
||||
|
* @returns |
||||
|
*/ |
||||
|
function optionGroup(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.name = props.name; // 用于指定控件的名称;如果处理直接提交到 URL 的表单时很有用
|
||||
|
result.keepColor = props.keepColor; // 当组件未勾选/关闭时,是否应保留颜色?
|
||||
|
result.type = props.optionGroupType; // 要使用的输入组件类型,默认radio,可选:radio、checkbox、toggle
|
||||
|
result.leftLabel = props.leftLabel; // 如有标签,应显示在组件的左侧
|
||||
|
result.inline = props.inline ?? true; // 将输入组件显示为内联块,而不是每个组件都有自己的行
|
||||
|
result.options = props.options; //具有值、标签和禁用(可选)属性的对象数组,包含的属性:label、value、disable等
|
||||
|
result.disable = props.disable; // 将组件置于禁用模式下
|
||||
|
result.size = props.size; // 带有 CSS 单位的尺寸大小,包括单位的名称或标准大小名称(xs | sm | md | lg | xl)
|
||||
|
result.color = props.color; // 组件的颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.dense = props.dense; // 紧凑模式,占用更少的空间
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 抽取表格组件属性 |
||||
|
* @param {Object} props 属性对象 |
||||
|
* @returns 表格组件属性 |
||||
|
*/ |
||||
|
export function extractTableProps(props: any) { |
||||
|
if (props) { |
||||
|
const result: any = {}; |
||||
|
result.color = props.color; // 组件的颜色,来自 Quasar 调色板的颜色名称
|
||||
|
result.dense = props.dense; // 密恐模式;
|
||||
|
result.dark = props.dark; // 设置组件背景为深色
|
||||
|
result.flat = props.flat; // 应用“平面”设计(无默认阴影)
|
||||
|
result.bordered = props.bordered; // 将默认边框应用于组件
|
||||
|
result.square = props.square; // 删除边框圆角(border-radius),使边框为正方形
|
||||
|
return Tools.pickNotNil(result); |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
function columnStyle(item: any) { |
||||
|
let style = ''; |
||||
|
if (Object.hasOwnProperty.call(item, 'style')) { |
||||
|
style = item.style; |
||||
|
} |
||||
|
if (Object.hasOwnProperty.call(item, 'width')) { |
||||
|
item.style = `min-width: ` + item.width + `px; max-width: ` + item.width + `px;` + style; |
||||
|
delete item.width; |
||||
|
|
||||
|
if (Object.hasOwnProperty.call(item, 'classes')) { |
||||
|
item.classes = item.classes + ' truncate'; |
||||
|
} else { |
||||
|
item.classes = 'truncate'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
function columnChildrenHandler(item: any, gridColumns: any) { |
||||
|
if (item.childrenColumns && item.childrenColumns.length > 0) { |
||||
|
item.childrenColumns.forEach((column) => { |
||||
|
columnChildrenHandler(column, gridColumns); |
||||
|
}); |
||||
|
} else { |
||||
|
columnStyle(item); |
||||
|
gridColumns.push({ |
||||
|
...{ align: 'left', label: item.name, field: item.name, sortable: true }, |
||||
|
...item, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
* 处理表格列属性 |
||||
|
*/ |
||||
|
export function extractTableColumnsProps(props: any) { |
||||
|
const gridColumns = <any>[]; |
||||
|
if (props.tableColumns && props.tableColumns.length > 0) { |
||||
|
if (props.tableShowSortNo) { |
||||
|
gridColumns.push({ name: '_sortNo_', align: 'center', label: '序号', field: '_sortNo_' }); |
||||
|
} |
||||
|
props.tableColumns.forEach((item: any) => { |
||||
|
columnChildrenHandler(item, gridColumns); |
||||
|
}); |
||||
|
return gridColumns; |
||||
|
} |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
const formColsScreenMap = new Map(); |
||||
|
formColsScreenMap.set(1, { xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }); |
||||
|
formColsScreenMap.set(2, { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }); |
||||
|
formColsScreenMap.set(3, { xs: 1, sm: 2, md: 3, lg: 4, xl: 6 }); |
||||
|
formColsScreenMap.set(4, { xs: 1, sm: 2, md: 4, lg: 4, xl: 6 }); |
||||
|
formColsScreenMap.set(5, { xs: 1, sm: 2, md: 5, lg: 6, xl: 6 }); |
||||
|
formColsScreenMap.set(6, { xs: 1, sm: 3, md: 6, lg: 6, xl: 6 }); |
||||
|
formColsScreenMap.set(7, { xs: 1, sm: 4, md: 7, lg: 7, xl: 7 }); |
||||
|
formColsScreenMap.set(8, { xs: 1, sm: 4, md: 8, lg: 8, xl: 8 }); |
||||
|
formColsScreenMap.set(9, { xs: 1, sm: 4, md: 9, lg: 9, xl: 9 }); |
||||
|
formColsScreenMap.set(10, { xs: 1, sm: 4, md: 10, lg: 10, xl: 10 }); |
||||
|
formColsScreenMap.set(11, { xs: 1, sm: 4, md: 11, lg: 11, xl: 11 }); |
||||
|
formColsScreenMap.set(12, { xs: 1, sm: 4, md: 12, lg: 12, xl: 12 }); |
||||
|
/** |
||||
|
* 根据设置的每列显示数、屏幕断点获取当前查询表单每行显示多少个字段 |
||||
|
* @param configColsNumber |
||||
|
* @param screen |
||||
|
*/ |
||||
|
export function getQueryFormColsNumberByScreen(configColsNumber: number, screen: any) { |
||||
|
return formColsScreenMap.get(configColsNumber)[screen]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 集合根据传入的key转map |
||||
|
* @param key |
||||
|
* @param array |
||||
|
*/ |
||||
|
export function arrayToMap(key, array) { |
||||
|
const map = new Map(); |
||||
|
array.forEach((item) => { |
||||
|
if (item.name !== '_sortNo_') { |
||||
|
map.set(item[key], item); |
||||
|
} |
||||
|
}); |
||||
|
return map; |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
export { PlatformIconEnum } from './commUtil'; |
||||
|
export { PageStatusEnum } from './commUtil'; |
||||
|
export { FormTypeEnum } from './commUtil'; |
||||
|
export { DataTypeEnum } from './commUtil'; |
||||
|
export { OptionComeFromEnum } from './commUtil'; |
||||
|
export { FormComponentValidateEnum } from './commUtil'; |
||||
|
export { OperatorTypeEnum } from './commUtil'; |
||||
|
export type { CriteriaType } from './commUtil'; |
||||
|
export { PlatformNotifyTypeEnum } from './commUtil'; |
||||
|
export { platformNotify } from './commUtil'; |
||||
|
export { isEmpty } from './commUtil'; |
||||
|
|
||||
|
export { extractDialogProps } from './componentComm'; |
||||
|
export { extractFormProps } from './componentComm'; |
||||
|
export { extractFormItemComponentProps } from './componentComm'; |
||||
|
export { extractTableProps } from './componentComm'; |
||||
|
export { extractTableColumnsProps } from './componentComm'; |
||||
|
export { getQueryFormColsNumberByScreen } from './componentComm'; |
||||
|
export { arrayToMap } from './componentComm'; |
@ -0,0 +1,48 @@ |
|||||
|
/** |
||||
|
* 平台icon,使用【material icons】,以下为列举的一些平台常用的图标,并不包含全部 |
||||
|
* 不在该枚举中时可使用自定义的图标,目的是以后方便全局替换。 |
||||
|
*/ |
||||
|
export enum IconEnum { |
||||
|
查询 = 'search', |
||||
|
更多查询 = 'zoom_in', |
||||
|
重置 = 'restart_alt', |
||||
|
刷新 = 'loop', |
||||
|
新增 = 'add', |
||||
|
新增2 = 'add_box', |
||||
|
新增3 = 'playlist_add_circle', |
||||
|
编辑 = 'edit', |
||||
|
删除 = 'delete', |
||||
|
查看 = 'visibility', |
||||
|
全屏 = 'fullscreen', |
||||
|
退出全屏 = 'fullscreen_exit', |
||||
|
关闭 = 'close', |
||||
|
保存 = 'save', |
||||
|
提交 = 'beenhere', |
||||
|
字母 = 'abc', |
||||
|
时钟 = 'access_time', |
||||
|
上箭头 = 'arrow_upward', |
||||
|
下箭头 = 'arrow_downward', |
||||
|
左箭头 = 'arrow_back', |
||||
|
右箭头 = 'arrow_forward', |
||||
|
附件 = 'attach_file', |
||||
|
否状态 = 'cancel', |
||||
|
是状态 = 'check_circle', |
||||
|
首页 = 'home', |
||||
|
设置 = 'settings', |
||||
|
设置2 = 'settings_applications', |
||||
|
扳手 = 'build_circle', |
||||
|
收起 = 'arrow_drop_up', |
||||
|
展开 = 'arrow_drop_down', |
||||
|
提示 = 'info', |
||||
|
警告 = 'warning', |
||||
|
日期 = 'event', |
||||
|
日期范围 = 'date_range', |
||||
|
文件夹 = 'folder', |
||||
|
校验 = 'published_with_changes', |
||||
|
下载 = 'download', |
||||
|
上传 = 'upload', |
||||
|
选取变量 = 'find_in_page', |
||||
|
报表通用 = 'assessment', |
||||
|
复制 = 'file_copy', |
||||
|
发送 = 'send', |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export { IconEnum } from './IconEnum'; |
@ -1,7 +1,7 @@ |
|||||
import login from './api/login.json'; |
import login from './api/login.json'; |
||||
import session from './api/system/user/session.json'; |
import session from './api/system/user/session.json'; |
||||
import appConfigure from './api/system/user/appConfigure.json'; |
import activeConfigure from './api/lcdp/configure/getActiveConfigure.json'; |
||||
|
|
||||
const PLATFORM_MOCKS = [login, session, appConfigure]; |
const PLATFORM_MOCKS = [login, session, activeConfigure]; |
||||
|
|
||||
export default PLATFORM_MOCKS; |
export default PLATFORM_MOCKS; |
||||
|
@ -1,9 +1,39 @@ |
|||||
<template> |
<template> |
||||
<div>{{ message }}</div> |
<w-dialog |
||||
|
ref="dialogRef" |
||||
|
:persistent="true" |
||||
|
:maximized="false" |
||||
|
title="xxx" |
||||
|
width="50%" |
||||
|
height="50%" |
||||
|
:can-maximize="false" |
||||
|
:buttons="[ |
||||
|
{ |
||||
|
icon: IconEnum.保存, |
||||
|
label: '保存', |
||||
|
loading: false, |
||||
|
click: async () => {}, |
||||
|
}, |
||||
|
]" |
||||
|
@maximized="dialogMaximize" |
||||
|
></w-dialog> |
||||
</template> |
</template> |
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { Environment, axios } from '@/platform'; |
// import { Environment, axios } from '@/platform'; |
||||
|
|
||||
const response = await axios.get(Environment.apiContextPath('/api/sample/action1')); |
// const response = await axios.get(Environment.apiContextPath('/api/sample/action1')); |
||||
const message = response.data.message; |
// const message = response.data.message; |
||||
|
|
||||
|
import { ref, reactive, onMounted } from 'vue'; |
||||
|
import { IconEnum } from '@/platform'; |
||||
|
|
||||
|
const dialogRef = ref(); |
||||
|
|
||||
|
const dialogMaximize = (maximizedToggle: boolean) => { |
||||
|
console.log(maximizedToggle); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
dialogRef.value.show(); |
||||
|
}); |
||||
</script> |
</script> |
||||
|
@ -1,10 +1,7 @@ |
|||||
<template> |
<template> |
||||
<DndProvider :backend="HTML5Backend"> |
|
||||
<w-platform-page></w-platform-page> |
<w-platform-page></w-platform-page> |
||||
</DndProvider> |
|
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { DndProvider } from 'vue3-dnd'; |
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'; |
|
||||
</script> |
</script> |
||||
|
@ -1,9 +1,39 @@ |
|||||
<template> |
<template> |
||||
<div>{{ message }}</div> |
<w-dialog |
||||
|
ref="dialogRef" |
||||
|
:persistent="true" |
||||
|
:maximized="false" |
||||
|
title="xxx" |
||||
|
width="50%" |
||||
|
height="50%" |
||||
|
:can-maximize="false" |
||||
|
:buttons="[ |
||||
|
{ |
||||
|
icon: IconEnum.保存, |
||||
|
label: '保存', |
||||
|
loading: false, |
||||
|
click: async () => {}, |
||||
|
}, |
||||
|
]" |
||||
|
@maximized="dialogMaximize" |
||||
|
></w-dialog> |
||||
</template> |
</template> |
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { Environment, axios } from 'platform-core'; |
// import { Environment, axios } from 'platform-core'; |
||||
|
|
||||
const response = await axios.get(Environment.apiContextPath('/api/sample/action1')); |
// const response = await axios.get(Environment.apiContextPath('/api/sample/action1')); |
||||
const message = response.data.message; |
// const message = response.data.message; |
||||
|
|
||||
|
import { ref, reactive, onMounted } from 'vue'; |
||||
|
import { IconEnum } from 'platform-core'; |
||||
|
|
||||
|
const dialogRef = ref(); |
||||
|
|
||||
|
const dialogMaximize = (maximizedToggle: boolean) => { |
||||
|
console.log(maximizedToggle); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
dialogRef.value.show(); |
||||
|
}); |
||||
</script> |
</script> |
||||
|
@ -1,10 +1,5 @@ |
|||||
<template> |
<template> |
||||
<DndProvider :backend="HTML5Backend"> |
<w-platform-page></w-platform-page> |
||||
<w-platform-page></w-platform-page> |
|
||||
</DndProvider> |
|
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"></script> |
||||
import { DndProvider } from 'vue3-dnd'; |
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'; |
|
||||
</script> |
|
||||
|
@ -1,10 +1,5 @@ |
|||||
<template> |
<template> |
||||
<DndProvider :backend="HTML5Backend"> |
<w-platform-page></w-platform-page> |
||||
<w-platform-page></w-platform-page> |
|
||||
</DndProvider> |
|
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"></script> |
||||
import { DndProvider } from 'vue3-dnd'; |
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'; |
|
||||
</script> |
|
||||
|
@ -0,0 +1,11 @@ |
|||||
|
[ |
||||
|
{ |
||||
|
"module" : "io.sc.platform.security", |
||||
|
"order" : 153, |
||||
|
"description" : "", |
||||
|
"properties": [ |
||||
|
"# - io.sc.platform.security", |
||||
|
"application.default-password = password" |
||||
|
] |
||||
|
} |
||||
|
] |
Loading…
Reference in new issue