提交 a2c1e38f 编写于 作者: RedPig97's avatar RedPig97

add: 新增搜索表单部件

上级 c976694e
{{#*inline "FORM"}}{{>@macro/front-end/views/view-control/view-form/view-control-form.hbs}}{{/inline}}
{{#*inline "SEARCHFORM"}}{{>@macro/front-end/views/view-control/view-form/view-control-form.hbs}}{{/inline}}
{{#*inline "SEARCHFORM"}}{{>@macro/front-end/views/view-control/view-search-form/view-control-search-form.hbs}}{{/inline}}
{{#*inline "GRID"}}{{>@macro/front-end/views/view-control/view-grid/view-control-grid.hbs}}{{/inline}}
{{#*inline "APPMENU"}}{{>@macro/front-end/views/view-control/view-menu/view-control-menu.hbs}}{{/inline}}
{{camelCase name}}:{
action:{
{{#each ctrl.psControlHandler.psHandlerActions as | action |}}
{{#eq action.name 'search'}}
searchAction: '{{action.psAppDEMethod.id}}',
{{/eq}}
{{#eq action.name 'load'}}
loadAction: '{{action.psAppDEMethod.id}}',
{{/eq}}
{{#eq action.name 'loaddraft'}}
loadDraftAction: '{{action.psAppDEMethod.id}}',
{{/eq}}
{{/each}}
}
}
<script setup lang="ts">
import { Subject } from 'rxjs';
import axios from 'axios';
import { onMounted, onActivated, onDeactivated, onUnmounted } from "vue";
import { onMounted, onActivated, onDeactivated, onUnmounted } from 'vue';
import { createUUID } from 'qx-util';
import tinymce from 'tinymce/tinymce';
// import 'tinymce/themes/modern';
......@@ -47,84 +47,94 @@ interface RichTextProps {
*/
viewParams: IParam;
/**
* @description 编辑器高度
*/
height: number;
/**
* 上传参数
* @description 上传参数
*
* @type {string}
* @memberof AppRichTextEditor
*/
uploadParams?: any;
/**
* 下载参数
* @description 下载参数
*
* @type {string}
* @memberof AppRichTextEditor
*/
exportParams?: any;
/**
* @description 是否禁用
*
*/
disabled: boolean;
}
interface EditorEmit {
(name: "editorEvent", value: IActionParam): void;
(name: 'editorEvent', value: IActionParam): void;
}
const props = withDefaults(defineProps<RichTextProps>(), {
disabled: false,
});
const emit = defineEmits<EditorEmit>();
const { getDownloadUrl, getRequestingHeader, getUploadUrl, getLocal, getUploadParams, getExportParams, getImgURLOfBase64 } = new EditorBase();
const {
getDownloadUrl,
getRequestingHeader,
getUploadUrl,
getLocal,
getUploadParams,
getExportParams,
getImgURLOfBase64,
} = new EditorBase();
const uploadUrl = getUploadUrl();
let downloadUrl = getDownloadUrl();
const headers = getRequestingHeader();
const uploadParams = getUploadParams(props.uploadParams,props.context, props.viewParams,props.data);
const exportParams = getExportParams(props.exportParams,props.context, props.viewParams,props.data);
const uploadParams = getUploadParams(props.uploadParams, props.context, props.viewParams, props.data);
const exportParams = getExportParams(props.exportParams, props.context, props.viewParams, props.data);
const uuid: string = createUUID();
const curLocal: string = getLocal();
let richTextEditor: any = null;
let isActivated: boolean = true;
const imgSrcList: any[] = [];
const loadingImgMap: Map<string,any> = new Map();
const successImgMap: Map<string,any> = new Map();
const loadingImgMap: Map<string, any> = new Map();
const successImgMap: Map<string, any> = new Map();
const getImgUrl = (html: any) => {
let imgs:Array<any>|null = html.match(/<img.*?(?:>|\/>)/gi)!=null? html.match(/<img.*?(?:>|\/>)/gi):[];
if(imgs && imgs.length > 0 && imgSrcList && imgSrcList.length > 0){
const getImgUrl = (html: any) => {
let imgs: Array<any> | null = html.match(/<img.*?(?:>|\/>)/gi) != null ? html.match(/<img.*?(?:>|\/>)/gi) : [];
if (imgs && imgs.length > 0 && imgSrcList && imgSrcList.length > 0) {
imgs.forEach((img: any, index: number) => {
var reg = /data:image\/.*;base64,.*(?=[\'\"])/;
if(img.match(reg) != null){
let base64:any = img.match(reg)[0];
if (img.match(reg) != null) {
let base64: any = img.match(reg)[0];
const _imgsrc = imgSrcList.find((_imgsrc: any) => Object.is(_imgsrc.key, base64));
if (_imgsrc) {
const newImg = img.replace(reg, '{'+_imgsrc.value+'}');
const newImg = img.replace(reg, '{' + _imgsrc.value + '}');
html = html.replace(img, newImg);
}
}
})
});
}
return html;
}
};
const getImgUrlBase64 = async (html: any) => {
let imgs:Array<any>|null = html.match(/<img.*?(?:>|\/>)/gi)!=null? html.match(/<img.*?(?:>|\/>)/gi):[];
if(imgs && imgs.length>0){
const getImgUrlBase64 = async (html: any) => {
let imgs: Array<any> | null = html.match(/<img.*?(?:>|\/>)/gi) != null ? html.match(/<img.*?(?:>|\/>)/gi) : [];
if (imgs && imgs.length > 0) {
for (let item of imgs) {
if(item.match(/src=[\'\"]?([^\'\"]*)[\'\"]?/ig)!=null){
let src:any = item.match(/src=[\'\"]?([^\'\"]*)[\'\"]?/ig)[0];
src = await getImgURLOfBase64(src.substring(5,src.length-1),loadingImgMap,successImgMap);
const image = item.replace(/src=[\'\"]?([^\'\"]*)[\'\"]?/i, 'src="'+src+'"');
if (item.match(/src=[\'\"]?([^\'\"]*)[\'\"]?/gi) != null) {
let src: any = item.match(/src=[\'\"]?([^\'\"]*)[\'\"]?/gi)[0];
src = await getImgURLOfBase64(src.substring(5, src.length - 1), loadingImgMap, successImgMap);
const image = item.replace(/src=[\'\"]?([^\'\"]*)[\'\"]?/i, 'src="' + src + '"');
html = html.replace(item, image);
}
}
}
return html;
}
};
const uploadFile = (url: string, formData: any) => {
const uploadFile = (url: string, formData: any) => {
const subject: Subject<any> = new Subject<any>();
axios({
method: 'post',
......@@ -143,8 +153,8 @@ const successImgMap: Map<string,any> = new Map();
subject.error(response);
});
return subject;
}
const init = () => {
};
const init = () => {
tinymceCode.init({
selector: '#' + uuid,
width: 'calc( 100% - 2px )',
......@@ -181,7 +191,7 @@ const successImgMap: Map<string,any> = new Map();
{ text: 'C++', value: 'cpp' },
],
paste_data_images: true,
codesample_content_css: 'assets/tinymce/prism.css',
codesample_content_css: './assets/tinymce/prism.css',
skin_url: './assets/tinymce/skins/lightgray/ui/oxide',
language_url: './assets/tinymce/langs/' + curLocal + '.js',
language: curLocal,
......@@ -190,9 +200,9 @@ const successImgMap: Map<string,any> = new Map();
editor.on('blur', () => {
let content = editor.getContent();
content = getImgUrl(content);
emit("editorEvent", {
emit('editorEvent', {
tag: props.name,
action: "valueChange",
action: 'valueChange',
data: content,
});
});
......@@ -216,18 +226,18 @@ const successImgMap: Map<string,any> = new Map();
if (file.filename) {
const id: string = file.fileid;
const url: string = `${downloadUrl}/${id}`;
getImgURLOfBase64(url,loadingImgMap,successImgMap).then((response: any) => {
const imgsrc = imgSrcList.find((imgsrc: any) => Object.is(response,imgsrc.key));
getImgURLOfBase64(url, loadingImgMap, successImgMap).then((response: any) => {
const imgsrc = imgSrcList.find((imgsrc: any) => Object.is(response, imgsrc.key));
if (!imgsrc) {
const item: any = { key: response, value: file.fileid+file.ext };
const item: any = { key: response, value: file.fileid + file.ext };
imgSrcList.push(item);
}
success(response);
let content = richTextEditor.getContent();
content = getImgUrl(content);
emit("editorEvent", {
emit('editorEvent', {
tag: props.name,
action: "valueChange",
action: 'valueChange',
data: content,
});
});
......@@ -245,21 +255,18 @@ const successImgMap: Map<string,any> = new Map();
(error: any) => {
console.log(error);
failure('HTTP Error: ' + error.status);
}
},
);
},
init_instance_callback: (editor: any) => {
richTextEditor = editor;
const url =
downloadUrl.indexOf('../') === 0
? downloadUrl.substring(3)
: downloadUrl;
const url = downloadUrl.indexOf('../') === 0 ? downloadUrl.substring(3) : downloadUrl;
let value = props.value && props.value.length > 0 ? props.value : '';
value = value.replace(
/\{(\w+)\.(bmp|jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp)\}/g,
`${url}/$1`
`${url}/$1`,
);
getImgUrlBase64(value).then((newValue: any) =>{
getImgUrlBase64(value).then((newValue: any) => {
if (richTextEditor) {
richTextEditor.setContent(newValue);
}
......@@ -269,27 +276,25 @@ const successImgMap: Map<string,any> = new Map();
});
},
});
}
};
onMounted(() => {
init();
});
onActivated(() => {
isActivated = true;
})
});
onDeactivated(() => {
isActivated = false;
})
});
onUnmounted(() => {
if (richTextEditor) {
tinymceCode.remove('#' + uuid);
}
})
}
});
</script>
<template>
<div :class="['app-editor-container','app-rich-text-editor', `app-rich-text-${name}`]">
<div :class="['app-editor-container', 'app-rich-text-editor', `app-rich-text-${name}`]">
<textarea :id="uuid"></textarea>
</div>
</template>
......@@ -393,7 +393,7 @@ export class FormControl extends MainControl {
}
return {
load: loadDraft,
loadDraft: loadDraft,
};
}
......
......@@ -7,3 +7,4 @@ export * from './grid-control'
export * from './pickupviewpanel-control'
export * from './tree-exp-bar-control'
export * from './tree-control'
export * from './search-form-control'
\ No newline at end of file
export * from './search-form-control-prop'
export * from './search-form-control-state'
export * from './search-form-control'
\ No newline at end of file
import { FormControlProps } from "@core";
/**
* @description 表单部件的props
* @export
* @interface SearchFormControlProps
* @extends {FormControlProps}
*/
export interface SearchFormControlProps extends FormControlProps {
}
\ No newline at end of file
import { FormControlState, IParam } from '@core';
/**
* @description 表单部件状态
* @export
* @interface SearchFormControlState
* @extends {FormControlState}
*/
export interface SearchFormControlState extends FormControlState {
}
import { deepCopy, FormControl, FormControlProps, FormControlState } from '@core';
/**
* @description 表单部件
* @export
* @class FormControl
* @extends {MainControl}
* @todo 部件加载loading,能否不写在行为方法里。
*/
export class SearchFormControl extends FormControl {
/**
* @description 部件状态
* @type {FormControlState}
* @memberof FormControl
*/
public declare controlState: FormControlState;
/**
* @description 查询
* @memberof SearchFormControl
*/
public onSearch() {
const { controlName } = this.controlState;
this.emit("ctrlEvent", {
tag: controlName,
action: "selectionChange",
data: this.controlState.data,
});
}
/**
* @description 取消
* @memberof SearchFormControl
*/
public onCancel() {
//todo 获取部件实列
}
/**
* @description 确认保存
* @memberof SearchFormControl
*/
public onOk() {
//todo saveModel服务
}
/**
* @description 安装部件所有功能模块的方法
* @param {FormControlProps} props 传入的Props
* @param {Function} [emit]
* @return {*}
* @memberof FormControl [emit] 事件
*/
public moduleInstall(props: FormControlProps, emit?: Function) {
const superParams = super.moduleInstall(props, emit);
// 表单行为能力启用
const { loadDraft } = this.useLoadDraft(props);
return {
...superParams,
state: this.controlState,
loadDraft,
onSearch: this.onSearch.bind(this),
onCancel: this.onCancel.bind(this),
onOk: this.onOk.bind(this),
handleEditorEvent: this.handleEditorEvent.bind(this),
handleComponentEvent: this.handleComponentEvent.bind(this),
};
}
}
.app-search-form {
&>.ant-row {
flex-direction: column;
.search-form-footer {
text-align: right;
}
}
&.right-button>.ant-row {
flex-flow: row wrap;
.search-form-content {
flex: auto;
}
}
.search-form-footer {
padding-left: 8px;
.ant-btn {
margin-right: 4px;
}
}
}
//搜索表单气泡
.search-form-popover-button {
margin-top: 8px;
display: flex;
justify-content: right;
.ant-btn {
margin-left: 4px;
}
}
\ No newline at end of file
......@@ -6,6 +6,9 @@ import { ViewConfig } from './{{spinalCase page.codeName}}-config';
{{#eq controlType "GRID"}}
import { {{codeName}}Grid } from '@widgets/{{spinalCase appEntity.codeName}}/{{spinalCase codeName}}-grid';
{{/eq}}
{{#eq controlType "SEARCHFORM"}}
import { {{codeName}}SearchForm } from '@widgets/{{spinalCase appEntity.codeName}}/{{spinalCase codeName}}-searchForm';
{{/eq}}
{{/page.ctrls}}
// props声明和默认值处理
......@@ -48,6 +51,17 @@ const { state, grid, handleCtrlEvent, handleToolbarEvent } = new GridView(ViewCo
@toolbarEvent="handleToolbarEvent"/>
</template>
{{/eq}}
{{#eq controlType "SEARCHFORM"}}
<template v-slot:searchForm>
<{{codeName}}SearchForm
:context="state.context"
:viewParams="state.viewParams"
:controlAction="state.{{camelCase name}}.action"
:viewSubject="state.viewSubject"
@ctrlEvent="handleCtrlEvent"
></{{codeName}}SearchForm>
</template>
{{/eq}}
{{#eq controlType "GRID"}}
<{{codeName}}Grid
ref="grid"
......
import {{ctrl.codeName}}SearchForm from "./{{spinalCase ctrl.codeName}}-searchForm.vue";
export { {{ctrl.codeName}}SearchForm };
{{>@macro/front-end/widgets/form-detail/include-form.hbs}}
import { ControlVOBase, EditFormService } from '@core';
import { {{pascalCase ctrl.psAppDataEntity.codeName}}Service } from '@api/{{spinalCase ctrl.psAppDataEntity.codeName}}/{{spinalCase ctrl.psAppDataEntity.codeName}}-service';
/**
* 部件展示数据对象
* @export
* @class ControlVO
*/
export class ControlVO extends ControlVOBase {
/**
* 用后台数据对象创建部件数据对象
* @param data 后台数据
*/
constructor(data: any){
super(data);
// 记录没有映射的属性
{{#if ctrl.psDEFormItems}}
this.$ownKeys =
{{~#each ctrl.psDEFormItems as | formItem | ~}}
{{#if @first}}[{{/if~}}
'{{lowerCase formItem.id}}'{{#unless @last}},{{/unless}}
{{~#if @last}}];{{/if~}}
{{/each}}
{{/if}}
}
// 表单里映射了属性的字段
{{#each ctrl.psDEFormItems as | formItem | }}
{{!-- TODO: 表单formItem的name拿不到 --}}
{{#neq formItem.psAppDEField null }}
get {{lowerCase formItem.id}}() {
return this.$DO.{{lowerCase formItem.psAppDEField.codeName}};
}
set {{lowerCase formItem.id}}(value: any) {
this.$DO.{{lowerCase formItem.psAppDEField.codeName}} = value;
}
{{/neq}}
{{/each}}
// 表单里没有映射实体属性的字段(srfuf除外)
{{#each ctrl.psDEFormItems as | formItem | }}
{{#if (and (eq formItem.psAppDEField null) (neq formItem.id "srfuf" )) }}
{{lowerCase formItem.id}}: any;
{{/if}}
{{/each}}
}
// 部件配置对象
export const CtrlConfig = {
controlCodeName: '{{ctrl.codeName}}',
controlName: '{{ctrl.name}}',
controlService: new EditFormService<ControlVO>(ControlVO, new {{pascalCase ctrl.psAppDataEntity.codeName}}Service() ),
data: new ControlVO({}),
detailsModel: {
{{#each ctrl.psDEFormPages as | FormPage | }}
{{>(lookup 'FORMDETAILSMODEL') items=FormPage.psDEFormDetails}}
{{/each}}
},
rules: {
{{#each ctrl.psDEFormEditItemVRs as | ruleItem |}}
{{#neq ruleItem 2}}
{{#if ruleItem.valueRuleType 'DEFVALUERULE'}}
{{psDEFormEditItemName}}: [
{{#each ruleItem.psDEFVRGroupCondition as | condition |}}
{trigger: ['change', 'blur'],
validator: (_rule: RuleObject, value: string) => verifyRules(_rule,value,{
{{#condition.condType}}type: '{{condition.condType}}',{{/condition.condType}}
{{#condition.name}}name: '{{condition.name}}',{{/condition.name}}
{{#condition.maxValue}}maxValue: '{{condition.maxValue}}',{{/condition.maxValue}}
{{#condition.minValue}}minValue: '{{condition.minValue}}',{{/condition.minValue}}
{{#condition.ruleInfo}}ruleInfo: '{{condition.ruleInfo}}',{{/condition.ruleInfo}}
{{#condition.includeMaxValue}}includeMaxValue: '{{condition.includeMaxValue}}',{{/condition.includeMaxValue}}
{{#condition.includeMinValue}}includeMinValue: '{{condition.includeMinValue}}',{{/condition.includeMinValue}}
}) }
{{/each}}
]
{{/if}}
{{/neq}}
{{/each}}
},
};
\ No newline at end of file
{{>@macro/front-end/widgets/form-detail/include-form.hbs}}
<script setup lang="ts">
import { Subject } from 'rxjs';
import { SaveOutlined } from '@ant-design/icons-vue';
import { CtrlConfig } from './{{spinalCase ctrl.codeName}}-searchForm-config';
import { SearchFormControl, IActionParam, IParam, ControlAction, IContext } from '@core';
interface Props {
context: IContext;
viewParams?: IParam;
controlAction: ControlAction;
showBusyIndicator?: boolean;
viewSubject: Subject<IActionParam>;
}
const props = withDefaults(defineProps<Props>(), {
viewSubject: () => new Subject<IActionParam>(),
showBusyIndicator: true,
})
// emit声明
interface CtrlEmit {
(name: "ctrlEvent", value: IActionParam): void;
}
const emit = defineEmits<CtrlEmit>();
// 安装功能模块,提供状态和能力方法
const { state, handleEditorEvent, handleComponentEvent, onSearch, loadDraft, onOk, onCancel } = new SearchFormControl(CtrlConfig).moduleInstall(props, emit);
</script>
<template>
<a-form
name="{{ctrl.codeName}}"
class="app-search-form{{#if ctrl.psSysCss}} {{ctrl.psSysCss.cssName}}{{/if}}{{#if ctrl.searchButtonPos}} {{ctrl.searchButtonPos}}-button{{/if}}"
style="{{#if ctrl.formWidth}}width: {{ctrl.formWidth}}px;{{/if}}"
:model="state.data"
:rules="state.rules">
<a-row>
<a-col>{{#if ctrl.noTabHeader}}
<a-row>
{{#each ctrl.psDEFormPages as | ctrlPage | }}
{{#each ctrlPage.psDEFormDetails as | formDetail | }}
{{>(lookup . 'formDetail.detailType') item=formDetail}}
{{/each }}
{{/each}}
{{else}}
<a-tabs class="ibiz-form-page">
{{#each ctrl.psDEFormPages as | ctrlPage | }}
{{>(lookup . 'ctrlPage.detailType') item=ctrlPage }}
{{/each}}
</a-tabs>
{{/if}}
</a-row>
</a-col>
<a-col class='search-form-footer'>
{{#if (eq ctrl.searchButtonStyle 'DEFAULT')}}
<a-button class='search-button' @click="onSearch" type="primary">搜索</a-button>
<a-button class='reset-button' @click='loadDraft'>重置</a-button>
<a-popover title="存储自定义查询">
<template #content>
<a-input />
<div class="search-form-popover-button">
<a-button @click="onCancel">取消</a-button>
<a-button @click="onOk" type="primary">保存</a-button>
</div>
</template>
<a-button><save-outlined /></a-button>
</a-popover>
{{else if (eq ctrl.searchButtonStyle 'SEARCHONLY')}}
<a-button class='search-button' type="primary">搜索</a-button>
{{/if}}
</a-col>
</a-row>
</a-form>
</template>
\ No newline at end of file
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册