高级查询组件存在缺陷、dockerfile文件提交、删除详情表单组件

pull/170/head
zhangdaiscott 2022-07-20 23:24:10 +08:00
parent 46c3c5d15d
commit 40009824d0
8 changed files with 54 additions and 771 deletions

View File

@ -19,7 +19,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
VITE_GLOB_API_URL=/jeecgboot
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://api3.boot.jeecg.com
VITE_GLOB_DOMAIN_URL=http://jeecg-boot-system:8080/jeecg-boot
# 接口父路径前缀
VITE_GLOB_API_URL_PREFIX=

30
Dockerfile Normal file
View File

@ -0,0 +1,30 @@
FROM nginx
MAINTAINER jeecgos@163.com
VOLUME /tmp
ENV LANG en_US.UTF-8
RUN echo "server { \
listen 80; \
location /jeecgboot { \
proxy_pass http://jeecg-boot-system:8080/jeecg-boot; \
proxy_redirect off; \
proxy_set_header Host jeecg-boot-system; \
proxy_set_header X-Real-IP \$remote_addr; \
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; \
} \
#解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \
location / { \
root /var/www/html/; \
index index.html index.htm; \
if (!-e \$request_filename) { \
rewrite ^(.*)\$ /index.html?s=\$1 last; \
break; \
} \
} \
access_log /var/log/nginx/access.log ; \
} " > /etc/nginx/conf.d/default.conf \
&& mkdir -p /var/www \
&& mkdir -p /var/www/html
ADD dist/ /var/www/html/
EXPOSE 80
EXPOSE 443

View File

@ -77,8 +77,29 @@ yarn build
```
## Docker镜像用法
- 编译项目
```bash
git clone https://github.com/jeecgboot/jeecgboot-vue3.git
cd jeecgboot-vue3
yarn install
yarn build
```
- 启动容器
```bash
docker build -t jeecgboot-vue3 .
docker run --name jeecgboot-vue3-nginx -p 80:80 -d jeecgboot-vue3
```
- host设置(127.0.0.1不行得设置具体IP)
```bash
192.168.5.100 jeecg-boot-system
```
## 功能模块

View File

@ -1,160 +0,0 @@
<template>
<div :class="formContainerClass">
<a-row>
<a-col v-for="(item, index) in schemas" :key="index" :span="getItemSpan(item)">
<div class="detail-item">
<div class="item-title" :title="item.label"> {{ item.label }} </div>
<div class="item-content" v-if="item.isHtml" v-html="detailFormData[item.field]"></div>
<div class="item-content" v-else-if="item.isImage">
<div class="ant-upload-list ant-upload-list-picture-card">
<template v-for="url in detailFormData[item.field]">
<div class="ant-upload-list-picture-card-container" style="margin-top: 8px">
<span>
<div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card" data-has-actions="true">
<div class="ant-upload-list-item-info">
<img :src="url" alt="图片不存在" class="ant-upload-list-item-image" />
</div>
<span class="ant-upload-list-item-actions">
<download-outlined @click="handleDownloadFile(url)" />
<eye-outlined @click="handleViewImage(item.field)" />
</span>
</div>
</span>
</div>
</template>
</div>
</div>
<div class="item-content" v-else-if="item.isFile">
<div class="ant-upload-list ant-upload-list-text">
<template v-for="url in detailFormData[item.field]">
<div class="">
<span>
<div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-text">
<div class="ant-upload-list-item-info">
<span>
<paper-clip-outlined />
<a :href="url" target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1">
{{ getFilename(url) }}
</a>
<span class="ant-upload-list-item-card-actions">
<download-outlined @click="handleDownloadFile(url)" />
</span>
</span>
</div>
</div>
</span>
</div>
</template>
</div>
</div>
<div v-else class="item-content">
{{ detailFormData[item.field] }}
</div>
</div>
</a-col>
</a-row>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { propTypes } from '/@/utils/propTypes';
import { useDetailForm } from '../hooks/useDetailForm';
import { DownloadOutlined, EyeOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'DetailForm',
components: {
DownloadOutlined,
EyeOutlined,
PaperClipOutlined,
},
props: {
span: propTypes.number.def(24),
//
schemas: propTypes.array.def([]),
//
data: propTypes.object.def({}),
containerClass: propTypes.string.def(''),
},
setup(props) {
const { formContainerClass, detailFormData, getItemSpan, handleDownloadFile, handleViewImage, getFilename } = useDetailForm(props);
return {
formContainerClass,
detailFormData,
getItemSpan,
handleDownloadFile,
handleViewImage,
getFilename,
};
},
});
</script>
<style scoped lang="less">
.jeecg-detail-form {
border: 1px solid #f0f0f0;
border-left: none;
border-bottom: none;
.detail-item {
display: flex;
flex-direction: row;
align-items: stretch;
line-height: 24px;
border-bottom: 1px solid #f0f0f0;
height: 100%;
.item-title {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
flex-grow: 0;
min-width: 100px;
width: 20%;
max-width: 220px;
background-color: #fafafa;
border-right: 1px solid #f0f0f0;
/* border-left: 1px solid #f0f0f0;*/
padding: 10px 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.item-content {
border-right: 1px solid #f0f0f0;
flex-grow: 1;
padding-left: 10px;
display: flex;
align-items: center;
justify-content: flex-start;
.anticon {
&:hover {
color: #40a9ff;
}
}
.detail-image-container {
box-sizing: border-box;
margin: 0;
padding: 0;
color: #000000d9;
font-size: 14px;
font-variant: tabular-nums;
list-style: none;
font-feature-settings: 'tnum';
line-height: 1.5715;
.image-item {
display: inline-block;
width: 104px;
height: 104px;
margin: 5px;
border: 1px solid #f0f0f0;
vertical-align: top;
img {
}
}
}
}
}
}
</style>

View File

@ -1,542 +0,0 @@
import { FormSchema, RenderCallbackParams } from '/@/components/Form';
import { computed, ref, watch } from 'vue';
import { getDictItemsByCode } from '/@/utils/dict';
import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil';
import { initDictOptions } from '/@/utils/dict/index';
import { loadDictItem, queryDepartTreeSync, getUserList } from '/@/api/common/api';
import { defHttp } from '/@/utils/http/axios';
import { FieldExtends } from '/@/views/super/online/cgform/types/onlineRender';
import { getAreaTextByCode } from '/@/components/Form/src/utils/Area';
import { JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types';
import { handleLinkDown, LINK_DOWN, OnlSubTab, useOnlineVxeTableColumns } from '/@/views/super/online/cgform/hooks/auto/useAutoForm';
import { pick } from 'lodash-es';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { createImgPreview } from '/@/components/Preview/index';
import { useMessage } from '/@/hooks/web/useMessage';
export interface DetailFormSchema {
field: string;
label: string;
span?: number;
view?: string;
isHtml?: boolean;
isImage?: boolean;
isFile?: boolean;
order?: any;
dictTable?: string;
dictText?: string;
dictCode?: string;
dict?: string;
fieldExtendJson?: string;
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
}
/*interface DetailFormProps {
span?: number;
schemas?: DetailFormSchema[];
data?: any;
containerClass?: string;
}*/
export function useDetailForm(props: any) {
console.log(props);
const dictOptionsMap = {};
const currentLinkFields: string[] = [];
const detailFormData = ref({});
const { createMessage } = useMessage();
const formContainerClass = computed(() => {
if (props.containerClass) {
return `jeecg-detail-form ${props.containerClass}`;
} else {
return 'jeecg-detail-form';
}
});
watch(
() => props.data,
async (formData) => {
if (formData) {
let arr = props.schemas;
let temp = {};
if (arr && arr.length > 0) {
for (let item of arr) {
let field = item.field;
try {
temp[field] = await getItemContent(item);
} catch (e) {
console.error('字段【' + field + '】文本获取失败', e);
}
}
}
detailFormData.value = temp;
console.log('23345', detailFormData.value);
}
},
{ deep: true, immediate: true }
);
async function getItemContent(item) {
let formData = props.data;
if (formData) {
let value = formData[item.field];
if (!value && value !== '0' && value !== 0) {
return '';
}
let str = value;
let view = item.view;
if (view == 'list' || view == 'radio' || view == 'checkbox' || view == 'list_multi') {
str = await getSelectText(item, formData);
} else if (view == 'sel_search') {
str = await getTableDataText(item, formData);
} else if (view == 'cat_tree') {
//分类字典树
str = await getCategoryDataText(item, formData);
} else if (view == 'sel_depart') {
//部门选择
str = await getDepartDataText(item, formData);
} else if (view == 'sel_user') {
// 用户选择
str = await getUserDataText(item, formData);
} else if (view == 'pca') {
//省市区
str = getAreaTextByCode(value);
} else if (view == 'link_down') {
//联动组件
str = await getLinkDownDataText(item, formData);
} else if (view == 'sel_tree') {
//自定义树控件
str = await getTreeDataText(item, formData);
} else if (view == 'switch') {
//开关组件
str = await getSwitchDataText(item, formData);
} else if (view == 'image' || view == 'file') {
str = getFileList(item, formData);
} else {
if (currentLinkFields.indexOf(item.field) >= 0) {
let arr = dictOptionsMap[item.field];
if (arr && arr.length > 0) {
str = filterMultiDictText(arr, value);
}
}
}
return str;
}
return '';
}
// 数据字典/表字典
async function getSelectText(item, formData) {
// 先从缓存取
let dictCode = getRequestDictCode(item);
let value = formData[item.field];
if (!dictCode) {
return value;
}
let options = getDictItemsByCode(dictCode);
if (options && options.length > 0) {
return filterMultiDictText(options, value);
} else {
let dictRes = [];
if (dictOptionsMap[dictCode]) {
dictRes = dictOptionsMap[dictCode];
} else {
//取不到再请求
dictRes = (await initDictOptions(dictCode)) || [];
}
if (dictRes && dictRes.length > 0) {
dictOptionsMap[dictCode] = dictRes;
return filterMultiDictText(dictRes, value);
}
}
return '';
}
function getRequestDictCode(item) {
let temp = '';
let { dictCode, dictTable, dictText } = item;
if (!dictTable) {
temp = dictCode;
} else {
temp = `${dictTable},${dictText},${dictCode}`;
}
return temp;
}
// 表字典-下拉搜索
async function getTableDataText(item, formData) {
let dictCode = getRequestDictCode(item);
let value = formData[item.field];
if (!value) {
return '';
}
let arr: any[] = [];
if (dictOptionsMap[dictCode + value]) {
arr = dictOptionsMap[dictCode + value];
} else {
//取不到再请求
arr = (await defHttp.get({ url: `/sys/dict/loadDictItem/${dictCode}`, params: { key: value } })) || [];
}
if (arr && arr.length > 0) {
dictOptionsMap[dictCode + value] = arr;
return arr.join(',');
//return filterMultiDictText(arr, value);
}
return '';
}
// 分类字典
async function getCategoryDataText(item, formData) {
let value = formData[item.field];
if (!value) {
return '';
}
let arr = (await loadDictItem({ ids: value })) || [];
if (arr && arr.length > 0) {
return arr.join(',');
}
return '';
}
// 部门数据
async function getDepartDataText(item, formData) {
let value = formData[item.field];
if (!value) {
return '';
}
let extend = getExtendConfig(item);
let storeField = extend.store || 'id';
let arr = (await queryDepartTreeSync({ ids: value, primaryKey: storeField })) || [];
if (arr && arr.length > 0) {
let temp: string[] = [];
for (let item of arr) {
temp.push(item.title);
}
return temp.join(',');
}
return '';
}
//用户数据
async function getUserDataText(item, formData) {
let value = formData[item.field];
if (!value) {
return '';
}
let extend = getExtendConfig(item);
let storeField = extend.store || 'username';
let params = {
[storeField]: value,
};
let res = (await getUserList(params)) || {};
let arr = res.records || [];
if (arr && arr.length > 0) {
let temp: string[] = [];
console.log('getUserDataText', arr);
let textField = extend.text || 'realname';
for (let item of arr) {
temp.push(item[textField]);
}
return temp.join(',');
}
return '';
}
function getExtendConfig(item) {
let extend: FieldExtends = {};
let { fieldExtendJson } = item;
if (fieldExtendJson) {
if (typeof fieldExtendJson == 'string') {
try {
let json = JSON.parse(fieldExtendJson);
extend = { ...json };
} catch (e) {
console.error(e);
}
}
}
return extend;
}
// 联动组件
async function getLinkDownDataText(item, formData) {
let { dictTable, field } = item;
let arr: any[] = [];
if (dictOptionsMap[field]) {
arr = dictOptionsMap[field];
} else {
if (dictTable) {
let json = JSON.parse(dictTable);
if (json) {
let { table, txt, key, linkField } = json;
let dictCode = `${table},${txt},${key}`;
let temp: any[] = (await initDictOptions(dictCode)) || [];
arr = [...temp];
if (arr && arr.length > 0) {
dictOptionsMap[field] = arr;
if (linkField) {
let fieldArray = linkField.split(',');
for (let item of fieldArray) {
dictOptionsMap[item] = arr;
currentLinkFields.push(item);
}
}
}
}
}
}
if (arr && arr.length > 0) {
let value = formData[field];
return filterMultiDictText(arr, value);
}
return '';
}
//自定义树
async function getTreeDataText(item, formData) {
let { dict, field } = item;
let arr = [];
if (dictOptionsMap[field]) {
arr = dictOptionsMap[field];
} else {
if (dict) {
arr = await initDictOptions(dict);
}
}
if (arr && arr.length > 0) {
let value = formData[field];
return filterMultiDictText(arr, value);
}
return '';
}
//开关
async function getSwitchDataText(item, formData) {
let { fieldExtendJson, field } = item;
let options = ['Y', 'N'];
if (fieldExtendJson) {
options = JSON.parse(fieldExtendJson);
}
let arr: any[] = [
{ value: options[0], text: '是' },
{ value: options[1], text: '否' },
];
let value = formData[field];
return filterMultiDictText(arr, value);
}
function getItemSpan(item) {
if (item.span) {
return item.span;
}
return props.span;
}
function getFileList(item, formData) {
let str = formData[item.field];
if (!str) {
return [];
}
let arr = str.split(',');
let result: string[] = [];
for (let item of arr) {
let src = getFileAccessHttpUrl(item) || '';
if (src) {
result.push(src);
}
}
return result;
}
function handleDownloadFile(url) {
if (url) {
window.open(url);
}
}
function handleViewImage(field) {
let values = detailFormData.value[field];
if (!values || values.length == 0) {
createMessage.warning('无图片!');
return;
}
createImgPreview({ imageList: values });
}
function getFilename(url) {
if (!url) {
return '';
}
return url.substring(url.lastIndexOf('/') + 1);
}
return {
formContainerClass,
detailFormData,
getItemSpan,
handleDownloadFile,
handleViewImage,
getFilename,
};
}
/**
* DetailFormSchema[online]
*/
export function getDetailFormSchemas(props) {
const detailFormSchemas = ref<DetailFormSchema[]>([]);
const refMap = {};
const hasSubTable = ref(false);
const subTabInfo = ref<OnlSubTab[]>([]);
const subDataSource = ref({});
const formSpan = computed(() => {
let temp = props.formTemplate;
if (temp == '2') {
return 12;
} else if (temp == '3') {
return 8;
} else if (temp == '4') {
return 6;
} else {
return 24;
}
});
function createFormSchemas(properties: any[]) {
//let properties:any[] = result.schema.properties
let subInfo: OnlSubTab[] = [];
console.log('111', properties);
let arr: DetailFormSchema[] = [];
let dataSourceObj = {};
Object.keys(properties).map((key) => {
const item = properties[key];
// uiSchema 无用
//const uiItem = this.uiSchema[key];// method、formTemplate、url
if (item.view == 'tab') {
hasSubTable.value = true;
let temp: OnlSubTab = {
key,
// 这个foreignKey是主表的字段
foreignKey: item['foreignKey'],
describe: item.describe,
relationType: item.relationType,
requiredFields: item.required || [],
order: item.order,
};
if (item.relationType == 1) {
refMap[key] = ref(null);
temp['properties'] = item.properties;
} else {
dealSubProerties(item);
refMap[key] = ref<JVxeTableInstance>();
temp['columns'] = item.columns;
dataSourceObj[key] = [];
}
subInfo.push(temp);
} else {
if (item.view === LINK_DOWN) {
let array = handleLinkDown(item, key);
for (let linkDownItem of array) {
let tempIndex = getFieldIndex(arr, linkDownItem.key);
let temp = {
field: linkDownItem.key,
label: linkDownItem.title,
view: linkDownItem.view,
order: linkDownItem.order,
};
if (tempIndex == -1) {
arr.push(temp);
} else {
arr[tempIndex] = temp;
}
}
} else if (item.view == 'hidden') {
//隐藏的不处理
} else {
let tempIndex = getFieldIndex(arr, key);
if (tempIndex == -1) {
let temp = Object.assign(
{
field: key,
label: item.title,
},
pick(item, ['view', 'order', 'fieldExtendJson', 'dictTable', 'dictText', 'dictCode', 'dict'])
);
if (item.view == 'file') {
temp['span'] = 24;
temp['isFile'] = true;
}
if (item.view == 'image') {
temp['span'] = 24;
temp['isImage'] = true;
}
if (item.view == 'umeditor' || item.view == 'markdown') {
temp['isHtml'] = true;
temp['span'] = 24;
}
arr.push(temp);
}
}
}
});
// 1.对arr排序
arr.sort(function (a, b) {
return a.order - b.order;
});
// 2.对子表排序
subInfo.sort(function (a, b) {
return a.order - b.order;
});
subTabInfo.value = subInfo;
for (let i = 0; i < arr.length; i++) {
let temp = arr[i];
if (temp.isFile === true || temp.isImage === true || temp.isHtml === true) {
if (i > 0) {
let last = arr[i - 1];
let span = last.span || formSpan.value;
last.span = span;
}
}
}
detailFormSchemas.value = arr;
subDataSource.value = dataSourceObj;
console.log('adadad', arr);
}
function dealSubProerties(subInfo) {
useOnlineVxeTableColumns(subInfo);
}
function getFieldIndex(arr: DetailFormSchema[], key: string) {
let index = -1;
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (item.field === key) {
index = i;
break;
}
}
return index;
}
return {
detailFormSchemas,
hasSubTable,
subTabInfo,
refMap,
createFormSchemas,
formSpan,
subDataSource,
};
}
/**
* TODO
* DetailFormSchema[]
*/
export function transDetailFormSchemas(formSchemas: FormSchema[]) {
const detailFormSchemas = ref<DetailFormSchema[]>([]);
console.log(formSchemas);
return detailFormSchemas;
}

View File

@ -5,7 +5,7 @@ import { useMessage } from '/@/hooks/web/useMessage';
import { Modal } from 'ant-design-vue';
import { createLocalStorage } from '/@/utils/cache';
import { useRoute } from 'vue-router';
import FormSchemaFactory from '/@/views/super/online/cgform/auto/comp/factory/FormSchemaFactory';
/**
*
*
@ -180,7 +180,7 @@ export function useSuperQuery() {
// 如果出现查询条件联动组件出来的场景,请跟踪此处
prop.view = view2QueryViewMap[prop.view];
}
let temp = FormSchemaFactory.createFormSchema(item.field, prop);
let temp;
// temp.setFormRef(formRef)
temp.noChange();
// 查询条件中的 下拉框popContainer为parentNode

View File

@ -1,64 +0,0 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" width="40%" title="详情" :showOkBtn="false" cancelText="关闭">
<detail-form :schemas="detailFormSchemas" :data="formData" :span="12"></detail-form>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { getDemoById } from './demo.api';
import DetailForm from '/@/components/Form/src/components/DetailForm.vue';
// Emits
const emit = defineEmits(['register']);
//
const formData = ref({});
//
const detailFormSchemas = [
{
field: 'name',
label: '名字',
},
{
field: 'keyWord',
label: '关键词',
},
{
field: 'punchTime',
label: '打卡时间',
},
{
field: 'salaryMoney',
label: '工资',
},
{
field: 'sex',
label: '性别',
view: 'list',
dictCode: 'sex',
},
{
field: 'age',
label: '年龄',
},
{
field: 'birthday',
label: '生日',
},
{
field: 'email',
label: '邮箱',
},
{
field: 'content',
label: '个人简介',
span: 24,
},
];
//
const [registerModal, { setModalProps }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false });
formData.value = await getDemoById({ id: data.record.id });
});
</script>

View File

@ -80,7 +80,6 @@
</template>
</BasicTable>
<DemoModal @register="registerModal" @success="reload" />
<DemoDetailModal @register="registerDetailModal" />
<JImportModal @register="registerModal1" :url="getImportUrl" online />
</div>
</template>
@ -98,7 +97,6 @@
import { useGo } from '/@/hooks/web/usePage';
import { router } from '/@/router';
import { filterObj } from '/@/utils/common/compUtils';
import DemoDetailModal from './DemoDetailModal.vue';
import SuperQuery from '/@/components/jeecg/super/superquery/SuperQuery.vue';
const go = useGo();