This commit is contained in:
吴先生 2023-07-04 16:54:37 +08:00
commit 4a4ff0df10
69 changed files with 24446 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@codemirror/autocomplete": "^6.7.1",
"@codemirror/lang-javascript": "^6.1.9",
"@codemirror/theme-one-dark": "^6.1.2",
"@element-plus/icons": "^0.0.11",
"axios": "^1.4.0",
"codemirror": "^6.0.1",
"element-plus": "^2.3.5",
"jsencrypt": "^3.3.2",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"pinia": "^2.1.3",
"sass": "^1.62.1",
"vform3-builds": "^3.0.10",
"vue": "^3.2.47",
"vue-codemirror": "^6.1.1",
"vue-router": "4",
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"unplugin-auto-import": "^0.16.2",
"unplugin-vue-components": "^0.24.1",
"vite": "^4.3.9"
}
}

BIN
public.zip Normal file

Binary file not shown.

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

5
src/App.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<router-view></router-view>
</template>

BIN
src/assets/images/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,207 @@
import * as SFCCompiler from '@vue/compiler-sfc';
const COMP_IDENTIFIER = `__sfc__`
export function compileFile(filename, code, compiled) {
if (!code.trim()) {
compiled.errors = []
return
}
if (!filename.endsWith('.vue')) {
compiled.js = compiled.ssr = code
compiled.errors = []
return
}
const id = hashId(filename)
const { errors, descriptor } = SFCCompiler.parse(code, {
filename,
sourceMap: true
})
// console.log(descriptor)
if (errors.length) {
compiled.errors = errors
return
}
if (
(descriptor.script && descriptor.script.lang) ||
(descriptor.scriptSetup && descriptor.scriptSetup.lang) ||
descriptor.styles.some((s) => s.lang) ||
(descriptor.template && descriptor.template.lang)
) {
compiled.errors = [
'lang="x" pre-processors are not supported in the in-browser playground.'
]
return
}
const hasScoped = descriptor.styles.some((s) => s.scoped)
let clientCode = ''
let ssrCode = ''
const appendSharedCode = (code) => {
clientCode += code
ssrCode += code
}
const clientScriptResult = doCompileScript(descriptor, id, false)
if (!clientScriptResult) {
return
}
const [clientScript, bindings] = clientScriptResult
clientCode += clientScript
// script ssr only needs to be performed if using <script setup> where
// the render fn is inlined.
if (descriptor.scriptSetup) {
const ssrScriptResult = doCompileScript(descriptor, id, true)
if (!ssrScriptResult) {
return
}
ssrCode += ssrScriptResult[0]
} else {
// when no <script setup> is used, the script result will be identical.
ssrCode += clientScript
}
// template
// only need dedicated compilation if not using <script setup>
if (descriptor.template && !descriptor.scriptSetup) {
const clientTemplateResult = doCompileTemplate(
descriptor,
id,
bindings,
false
)
if (!clientTemplateResult) {
return
}
clientCode += clientTemplateResult
const ssrTemplateResult = doCompileTemplate(descriptor, id, bindings, true)
if (!ssrTemplateResult) {
return
}
ssrCode += ssrTemplateResult
}
if (hasScoped) {
appendSharedCode(
`\n${COMP_IDENTIFIER}.__scopeId = ${JSON.stringify(`data-v-${id}`)}`
)
}
if (clientCode || ssrCode) {
appendSharedCode(
`\n${COMP_IDENTIFIER}.__file = ${JSON.stringify(filename)}` +
`\nexport default ${COMP_IDENTIFIER}`
)
compiled.js = clientCode.trimStart()
compiled.ssr = ssrCode.trimStart()
}
// styles
let css = ''
for (const style of descriptor.styles) {
if (style.module) {
compiled.errors = [`<style module> is not supported in the playground.`]
return
}
const styleResult = SFCCompiler.compileStyle({
source: style.content,
filename,
id,
scoped: style.scoped,
modules: !!style.module
})
if (styleResult.errors.length) {
// postcss uses pathToFileURL which isn't polyfilled in the browser
// ignore these errors for now
if (!styleResult.errors[0].message.includes('pathToFileURL')) {
compiled.errors = styleResult.errors
}
// proceed even if css compile errors
} else {
css += styleResult.code + '\n'
}
}
if (css) {
compiled.css = css.trim()
} else {
compiled.css = '/* No <style> tags present */'
}
// clear errors
compiled.errors = []
}
function doCompileScript(descriptor, id, ssr) {
if (descriptor.script || descriptor.scriptSetup) {
try {
const compiledScript = SFCCompiler.compileScript(descriptor, {
id,
refSugar: true,
inlineTemplate: true,
templateOptions: {
ssr,
ssrCssVars: descriptor.cssVars
}
})
let code = ''
if (compiledScript.bindings) {
code += `\n/* Analyzed bindings: ${JSON.stringify(
compiledScript.bindings,
null,
2
)} */`
}
code +=
`\n` +
SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER)
// console.log( SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER))
return [code, compiledScript.bindings]
} catch (e) {
console.log('recordFileErrors',e)
recordFileErrors([e])
return
}
} else {
return [`\nconst ${COMP_IDENTIFIER} = {}`, undefined]
}
}
function doCompileTemplate(descriptor, id, bindingMetadata, ssr) {
const templateResult = SFCCompiler.compileTemplate({
source: descriptor.template && descriptor.template.content,
filename: descriptor.filename,
id,
scoped: descriptor.styles.some(s => s.scoped),
slotted: descriptor.slotted,
ssr,
ssrCssVars: descriptor.cssVars,
isProd: false,
compilerOptions: {
bindingMetadata
}
})
if (templateResult.errors.length) {
recordFileErrors(templateResult.errors)
return
}
const fnName = ssr ? `ssrRender` : `render`
return (
`\n${templateResult.code.replace(
/\nexport (function|const) (render|ssrRender)/,
`$1 ${fnName}`
)}` + `\n${COMP_IDENTIFIER}.${fnName} = ${fnName}`
)
}
function hashId(filename) {
return btoa(filename).slice(0, 8)
}

43
src/compiler/testCode.js Normal file
View File

@ -0,0 +1,43 @@
export const table = `
<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column type="index" :index="indexMethod" label="序列" width="100" />
<el-table-column prop="cgPerson" label="名称" />
<el-table-column prop="status" label="状态" />
</el-table>
</template>
<script setup >
import { ref,getCurrentInstance } from 'vue'
const { proxy } =getCurrentInstance();
const indexMethod = (index) => {
return index + 1
}
const tableData = ref([])
const params = ref({
page:1,
pageSize:10,
orderNo:''
})
const getList = ()=>{
proxy.$http.get('/api/data/table/CgOrder',params.value).then(res=>{
if(res.code == 200){
console.log('res',res.data)
tableData.value = res.data
}
})
}
getList()
</script>
<style scoped>
.bg1 {
font-size: 30px;
color: red;
}
</style>
`;

View File

@ -0,0 +1,116 @@
<template>
<div class="dept_content">
<el-icon class="add" :size="30" color="#409eff" @click="clickAdd"><CirclePlus /></el-icon>
<div class="dept_list">
<el-tag
v-for="(tag,index) in selectDepartment"
:key="tag"
class="mx-1"
closable
:disable-transitions="false"
@close="handleClose(index)"
>
{{ tag.label }}
</el-tag>
</div>
</div>
<el-dialog v-model="outerVisible" title="选择部门">
<el-tree
ref="treeRef"
:data="options"
show-checkbox
node-key="key"
@check-change="handleCheckChange"
:default-checked-keys="defaultCheckedKeys"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="outerVisible = false">取消</el-button>
<el-button type="primary" @click="submit"
>确定</el-button
>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref ,toRefs,nextTick} from 'vue';
const props = defineProps({
options:{
type: Array,
default: []
},
model: {
type: Object,
default: {}
},
name:{
type:String,
default:''
}
});
const {options,model,name} = toRefs(props)
const outerVisible = ref(false)
const defaultCheckedKeys= ref([])
const treeRef = ref()
const selectDepartment = ref(model.value[name.value+'Array']||[])
const clickAdd = ()=>{
let checkedKeys = []
console.log(selectDepartment.value)
selectDepartment.value.map(item=>{
checkedKeys.push(item.key)
})
outerVisible.value = true
nextTick(()=>{
treeRef.value.setCheckedKeys(checkedKeys, false)
})
}
// const defaultCheckedKeys = (item)=>{
// return model.value.split(',')
// }
const submit = ()=>{
let checkedNodes = treeRef.value.getCheckedNodes(true)
selectDepartment.value = checkedNodes
let checkedKeys= treeRef.value.getCheckedKeys(true)
model.value[name.value] = checkedKeys.toString()
outerVisible.value = false
}
const handleCheckChange = (data, checked)=>{
console.log(data,checked)
}
const handleClose = (index)=>{
selectDepartment.value.splice(index,1)
let checkedKeys = []
selectDepartment.value.map(item=>{
checkedKeys.push(item.key)
})
model.value[name.value] = checkedKeys.toString()
}
</script>
<style lang="scss" scoped>
.dept_content{
line-height: 1;
.add{
cursor: pointer;
}
.dept_list{
margin-top: 5px;
span{
margin-right: 5px;
}
}
}
</style>

View File

@ -0,0 +1,40 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped >
.read-the-docs {
color: #888;
}
</style>

View File

@ -0,0 +1,202 @@
<template>
<el-input
class="ydool_input"
:name="data.name"
v-if="data.type == 'text'"
v-model="model[data.name]"
:autocomplete="data.autocomplete"
clearable
:placeholder="data.label"
:readonly="data.readonly"
/>
<el-input
class="ydool_input"
v-else-if="data.type == 'textarea'"
type="textarea"
v-model="model[data.name]"
:rows="4"
clearable
:placeholder="data.label"
/>
<el-input-number
class="ydool_input"
v-else-if="data.type == 'number'"
v-model="model[data.name]"
:placeholder="data.label"
/>
<el-select
class="ydool_select"
v-else-if="data.type == 'select'"
clearable
v-model="model[data.name]"
:placeholder="data.label"
>
<el-option
v-for="item in data.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-select
class="ydool_select"
v-else-if="data.type == 'mapping'"
clearable
v-model="model[data.name]"
:placeholder="data.label"
>
<el-option
:label="model[`${data.name}_empty`]"
value=""
v-if="model[`${data.name}_empty`]"
/>
<el-option
v-for="item in model[`${data.name}_mapping`]"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-radio-group v-else-if="data.type == 'bool'" v-model="model[data.name]">
<el-radio :label="true">{{ data.dict.true }}</el-radio>
<el-radio :label="false">{{ data.dict.false }}</el-radio>
</el-radio-group>
<el-select
class="ydool_select"
v-else-if="data.type == 'dill'"
clearable
v-model="model[data.name]"
:placeholder="data.label"
@change="dillChange(data)"
>
<el-option
v-for="item in data.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-radio-group v-else-if="data.type == 'radio'" v-model="model[data.name]">
<el-radio
:label="option.value"
v-for="option in data.options"
:key="option.value"
>{{ option.label }}</el-radio
>
</el-radio-group>
<el-radio-group
v-else-if="data.type == 'radio-group'"
v-model="model[data.name]"
>
<el-radio-button
:label="option.value"
v-for="option in data.options"
:key="option.value"
>{{ option.label }}</el-radio-button
>
</el-radio-group>
<el-date-picker
class="ydool_input"
v-else-if="data.type == 'date'"
v-model="model[data.name]"
type="date"
:placeholder="data.label"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
<el-date-picker
class="ydool_input"
v-else-if="data.type == 'datetime'"
v-model="model[data.name]"
type="datetime"
:placeholder="data.label"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD H:m:s"
/>
<el-autocomplete
clearable
v-else-if="data.type == 'autocomplete'"
v-model="model[data.name]"
:fetch-suggestions="
(queryString, cb) => handleFilter(data.options, queryString, cb)
"
class="inline-input ydool_input"
:placeholder="data.placeholder"
@select="(option) => hanleOnSelect(option, data.onSelect)"
/>
<el-tree-select
class="ydool_input"
v-model="model[data.name]"
:data="data.options"
clearable
v-else-if="data.type == 'tree-select'"
placeholder="请选择"
check-strictly
/>
<div v-else-if="data.type == 'tree-dialog'" class="dept_content">
<Division :name="data.name" :model="model" :options="data.options" />
</div>
<Upload v-else-if="data.type == 'image'"
v-model="model[data.name]"> </Upload>
<Uploads v-else-if="data.type == 'images'"
v-model="model[data.name]"> </Uploads>
<File v-else-if="data.type == 'file'" v-model="model[data.name]"></File>
</template>
<script setup>
import { toRefs } from "vue";
import http from "../../utils/request";
import emitter from "@/plugins/Bus";
import Division from "@/components/Division";
import Upload from '@/components/Upload';
import Uploads from '@/components/Upload/Images.vue';
const props = defineProps({
data: {
type: Object,
default: {},
},
model: {
type: Object,
default: {},
},
});
const { data, model } = toRefs(props);
const dillChange = (item) => {
http
.get(`/api/data/dill/${item.grapeName}`, {
id: model.value[data.value.name],
})
.then((res) => {
console.log(res.data);
emitter.emit(item.dillName, res.data);
});
};
emitter.on(data.value.name, (e) => {
data.value.options = e;
});
const calendarChange = () => {
console.log("1212");
};
</script>
<style lang="scss">
.ydool_input,
.ydool_select {
width: 100% !important;
}
.dept_content {
line-height: 1;
.dept_list {
display: flex;
}
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<el-table :data="tableData.data" border style="width: 100%">
<el-table-column :label="item.label" :width="item.width" v-for="(item,index) in tableData.column" :key="index">
<template #default="scope">
<div style="display: flex; align-items: center">
<span style="margin-left: 10px">{{ scope.row[item.name]?scope.row[item.name]:'暂无' }}</span>
</div>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
import {ref,toRefs} from 'vue'
const props = defineProps({
tableData: {
type: Object,
default: {}
}
});
const {tableData} = toRefs(props)
</script>

View File

@ -0,0 +1,193 @@
<template>
<el-upload
class="avatar-uploader"
:accept="accept"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:http-request="request"
>
<el-button type="primary">附件上传</el-button>
<div class="fileBox" v-for="(item, index) in fileList" :key="index" @click.stop="openPath(item)">
<span>{{ handleName(item) }}</span>
<el-icon
class="fileBoxDel"
:size="18"
color="red"
@click.stop="delImageUrl(index)"
><CircleCloseFilled
/></el-icon>
</div>
</el-upload>
</template>
<script>
import { ref, watch, watchEffect, toRefs } from "vue";
import http from "../../utils/request";
export default {
name: "Upload",
props: {
modelValue: {
type: String,
default: "",
},
accept: {
type: String,
default: "image/gif, image/jpeg, image/png",
},
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const fileList = ref([]);
const toStr = (arr) => {
let str = arr.join(",");
return str;
};
const toArr = (str) => {
console.log(str);
let _arr = [];
if (str == null || str == "") {
_arr = [];
} else {
if (Array.isArray(str)) {
str.map((item) => {
_arr.push(item.preview);
});
} else {
_arr = str.split(",");
}
}
return _arr;
};
watch(
() => [...fileList.value],
(data) => {
emit("update:modelValue", toStr(fileList.value));
}
); //
watch(
() => props.modelValue,
() => {
fileList.value = toArr(props.modelValue);
},
{
immediate: true,
}
);
const request = (param) => {
let file = param.file;
const data = new FormData();
data.append("file", file);
http.post("/api/attachment/upload", data).then((res) => {
if (res.code == 200) {
fileList.value.push(res.data.path);
}
});
};
const handleAvatarSuccess = () => {};
const handleRemove = () => {};
const delImageUrl = (index) => {
fileList.value.splice(index, 1);
};
const handleName = (path)=>{
let Arr = path.split('/')
return Arr[Arr.length-1]
}
const openPath = (path)=>{
window.open(path)
}
return {
fileList,
request,
...toRefs(props),
handleAvatarSuccess,
handleRemove,
delImageUrl,
handleName,
openPath
};
},
};
</script>
<style lang="scss">
.avatar-uploader {
line-height: 1;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
transition: var(--el-transition-duration-fast);
line-height: 1;
font-size: 0;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 80px;
height: 80px;
text-align: center;
border: 1px dashed var(--el-border-color);
}
.avatar {
width: 80px;
height: 80px;
}
.el-upload-list__item-actions {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.fileBox {
position: relative;
margin-top: 5px;
padding:10px 10px;
&:hover{
background-color: rgba($color: #000000, $alpha: 0.1);
.fileBoxDel{
display: block;
}
}
}
.fileBox {
display: block;
width: 100%;
font-size: 0;
line-height: 1;
span{
width: 80%;
display: inline-block;
font-size: 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.avatar-uploader{
width: 100%;
}
.fileBoxDel{
display: none;
position: absolute;
right: 2px;
top: 50%;
transform: translateY(-50%);
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<el-upload
class="avatar-uploader"
:accept="accept"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:http-request="request"
>
<div class="imgBox" v-for="(item, index) in fileList" :key="index" @click.stop="openPath(item)" >
<el-image :src="item" class="avatar" />
<el-icon
class="del"
:size="18"
color="red"
@click.stop="delImageUrl(index)"
><CircleCloseFilled
/></el-icon>
</div>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</template>
<script>
import { ref, watch, watchEffect, toRefs } from "vue";
import http from "../../utils/request";
export default {
name: "Upload",
props: {
modelValue: {
type: String,
default: "",
},
accept: {
type: String,
default: "image/gif, image/jpeg, image/png",
},
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const fileList = ref([]);
const toStr = (arr) => {
let str = arr.join(",");
return str;
};
const toArr = (str) => {
console.log(str);
let _arr = [];
if (str == null || str == "") {
_arr = [];
} else {
if(Array.isArray(str)){
str.map(item=>{
_arr.push(item.preview)
})
}else{
_arr = str.split(",");
}
}
return _arr;
};
watch(
() => [...fileList.value],
(data) => {
emit("update:modelValue", toStr(fileList.value));
}
); //
watch(
() => props.modelValue,
() => {
fileList.value = toArr(props.modelValue);
},
{
immediate: true,
}
);
const request = (param) => {
let file = param.file;
const data = new FormData();
data.append("file", file);
http.post("/api/attachment/upload", data).then((res) => {
if (res.code == 200) {
fileList.value.push(res.data.path);
}
});
};
const handleAvatarSuccess = () => {};
const handleRemove = () => {};
const delImageUrl = (index) => {
fileList.value.splice(index, 1);
};
const openPath = (path)=>{
window.open(path)
}
return {
fileList,
request,
...toRefs(props),
handleAvatarSuccess,
handleRemove,
delImageUrl,
openPath
};
},
};
</script>
<style lang="scss">
.avatar-uploader {
line-height: 1;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
transition: var(--el-transition-duration-fast);
line-height: 1;
font-size: 0;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 80px;
height: 80px;
text-align: center;
border: 1px dashed var(--el-border-color);
}
.avatar {
width: 80px;
height: 80px;
}
.el-upload-list__item-actions {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.del {
position: absolute;
right: 0;
top: 0;
transform: translate(30%, -30%);
}
.imgBox {
position: relative;
margin-right: 5px;
margin-bottom: 5px;
border: 1px dashed var(--el-border-color);
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<el-upload
class="avatar-uploader"
:accept="accept"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:http-request="request"
>
<div v-if="imageUrl" @click.stop="openPath(imageUrl)" style="position: relative;">
<el-image :src="imageUrl" class="avatar" />
<el-icon class="del" :size="18" color="red" @click.stop="delImageUrl"
><CircleCloseFilled
/></el-icon>
</div>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</template>
<script>
import { ref, watch, watchEffect, toRefs } from "vue";
import http from "../../utils/request";
export default {
name: "Upload",
props: {
modelValue: {
type: String,
default: "",
},
accept: {
type: String,
default: "image/gif, image/jpeg, image/png",
},
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const imageUrl = ref(props.modelValue);
const toArr = (str) => {
console.log(str);
let path = "";
if (str == null || str == "") {
path = "";
} else {
console.log(typeof str);
if (typeof str == "object") {
console.log(str);
path = str.preview;
} else {
path = str;
}
}
console.log("path", path);
return path;
};
watch(
() => imageUrl.value,
(data) => {
emit("update:modelValue", imageUrl.value);
}
); //
watch(
() => props.modelValue,
() => {
console.log("imageUrl", props.modelValue);
imageUrl.value = toArr(props.modelValue);
},
{
immediate: true,
}
);
const request = (param) => {
let file = param.file;
console.log(file);
const data = new FormData();
data.append("file", file);
http.post("/api/attachment/upload", data).then((res) => {
if (res.code == 200) {
imageUrl.value = res.data.path;
}
});
};
const handleAvatarSuccess = () => {};
const handleRemove = () => {};
const delImageUrl = () => {
imageUrl.value = "";
};
const openPath = (path) => {
window.open(path);
};
return {
imageUrl,
request,
...toRefs(props),
handleAvatarSuccess,
handleRemove,
delImageUrl,
openPath
};
},
};
</script>
<style lang="scss">
.avatar-uploader {
line-height: 1;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
transition: var(--el-transition-duration-fast);
line-height: 1;
font-size: 0;
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 80px;
height: 80px;
text-align: center;
border: 1px dashed var(--el-border-color);
}
.avatar {
width: 80px;
height: 80px;
}
.el-upload-list__item-actions {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.del {
position: absolute;
right: 0;
top: 0;
transform: translate(30%, -30%);
}
</style>

View File

10
src/components/index.js Normal file
View File

@ -0,0 +1,10 @@
import { defineAsyncComponent } from 'vue';
const components = import.meta.glob('./**/*.vue')
export default function install(app) {
for (const [key, value] of Object.entries(components)) {
const name = key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.'))
app.component(name, defineAsyncComponent(value))
}
}

89
src/main.js Normal file
View File

@ -0,0 +1,89 @@
import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import ElementPlus from "element-plus"; //引入element-plus库
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import VForm3 from "vform3-builds"; //引入VForm3库
import "element-plus/dist/index.css"; //引入element-plus样式
import "vform3-builds/dist/designer.style.css"; //引入VForm3样式
import { appComponent } from "@/scripts/dynamicComponent";
import "@/scripts/grape-import";
import { table } from "@/compiler/testCode";
import http from "@/utils/request.js";
const app = createApp(App);
var loadDynamicComponent = false;
var loadInfo = false;
const viewModules = import.meta.glob("./views/**/**.vue");
console.log(viewModules);
router.beforeEach(async (to, from) => {
if (!loadDynamicComponent) {
loadDynamicComponent = true;
await initDynamicComponent();
}
});
const initDynamicComponent = () => {
// 解析创建组件
appComponent(app, { name: "table", code: table });
appComponent(app, { name: "table2", code: table });
};
router.beforeEach(async (to, from, next) => {
if (loadInfo) {
next();
} else {
// 第一次创建路由
let listRouter = [
{
path: "/table",
meta: {
title: "测试1",
name: "table",
},
},
{
path: "/table2",
meta: {
title: "测试2",
name: "table2",
},
},
];
listRouter.map((item) => {
item.component = loadView("/common/component");
item.props = { name: item.meta.name, title: item.meta.title };
router.addRoute("layout", item);
});
console.log("layout", router);
loadInfo = true;
next({ ...to, replace: true });
}
});
export const loadView = (view) => {
view = view.substring(0, 1) === "/" ? view : "/" + view;
console.log(`./views/${view}.vue`);
return viewModules[/* @vite-ignore */ `./views${view}.vue`];
};
app.config.globalProperties.$http = http;
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus).use(VForm3).use(createPinia()).use(router).mount("#app");

4
src/plugins/Bus.js Normal file
View File

@ -0,0 +1,4 @@
import mitt from 'mitt';
const emitter = mitt()
export default emitter

View File

@ -0,0 +1 @@
module.exports = file => require('../views/' + file + '.vue').default

160
src/router/index.js Normal file
View File

@ -0,0 +1,160 @@
import { createRouter, createWebHashHistory } from "vue-router";
import tools from "@/utils/tools.js";
const modules = import.meta.glob("../views/**/**.vue");
const routes = [
{
path: "/login",
name: "login",
mete: {
title: "登录",
},
component: () => import("../views/login/index.vue"),
},
{
path: "/",
component: () => import("../views/layout/index.vue"),
name: "layout",
redirect: {
name: "home",
},
meta: {
title: "主页",
showOnly: true,
},
children: [
{
path: "home",
component: () => import("../views/home/index.vue"),
name: "home",
meta: { title: "欢迎" },
},
],
},
{
path: "/formDesigner",
name: "formDesigner",
mete: {
title: "VForm 3",
},
component: () => import("../views/formDesigner/index.vue"),
},
{
path: "/codemirror",
name: "codemirror",
mete: {
title: "编辑器",
},
component: () => import("../views/codemirror/index.vue"),
},
// {
// path: "/data-flow-editor",
// name: "data-flow-editor",
// mete: {
// title: "数据中台",
// },
// component: () => import("../views/data-flow-editor/index.vue"),
// },
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
var isGetApiRouter = false;
router.beforeEach((to, form, next) => {
if (to.path === "/login") {
isGetApiRouter = false;
next();
return false;
}
let user = tools.data.get("user");
if (!user) {
next({
path: "/login",
});
return false;
}
if (!isGetApiRouter) {
var apiRouter = filterAsyncRouter(user.menus);
apiRouter = flatAsyncRoutes(apiRouter);
console.log("apiRouter", apiRouter);
apiRouter.forEach((item) => {
router.addRoute("layout", item);
});
isGetApiRouter = true;
next({ ...to, replace: true });
}
next();
});
//路由扁平化
function flatAsyncRoutes(routes, breadcrumb = []) {
let res = [];
routes.forEach((route) => {
const tmp = { ...route };
if (tmp.children) {
let childrenBreadcrumb = [...breadcrumb];
childrenBreadcrumb.push(route);
let tmpRoute = { ...route };
tmpRoute.meta.breadcrumb = childrenBreadcrumb;
delete tmpRoute.children;
res.push(tmpRoute);
let childrenRoutes = flatAsyncRoutes(tmp.children, childrenBreadcrumb);
childrenRoutes.map((item) => {
res.push(item);
});
} else {
let tmpBreadcrumb = [...breadcrumb];
tmpBreadcrumb.push(tmp);
tmp.meta.breadcrumb = tmpBreadcrumb;
res.push(tmp);
}
});
return res;
}
//转换
function filterAsyncRouter(routerMap) {
const accessedRouters = [];
routerMap.forEach((item) => {
item.meta = item.meta ? item.meta : {};
if (JSON.stringify(item.meta) != "{}") {
item.meta.key = item.path;
}
//处理外部链接特殊路由
if (item.meta.type == "iframe") {
item.meta.url = item.path;
item.path = `/i/${item.name}`;
}
//MAP转路由对象
var route = {
path: item.path,
name: item.name,
meta: item.meta,
redirect: item.redirect,
children: item.children ? filterAsyncRouter(item.children) : null,
component: loadComponent(item.component),
};
accessedRouters.push(route);
});
return accessedRouters;
}
function loadComponent(component) {
if (component) {
return modules[/* @vite-ignore */ `../views/${component}`];
} else {
return modules[/* @vite-ignore */ `../views/other/empty.vue`];
}
}
export default router;

View File

@ -0,0 +1,58 @@
import { babelParse } from "@vue/compiler-sfc";
import { compileFile } from "../compiler/sfc-compiler.js";
import { ElMessage } from 'element-plus'
export function appComponent(app, item) {
var compiled = {};
compileFile("TestCode.vue", item.code, compiled);
if (compiled.errors.length > 0) {
ElMessage({
type: "error",
duration: 0,
showClose: true,
message: `
组件${item.name}发生错误
${compiled.errors[0]}
`,
});
console.error(`组件“${item.name}”发生错误`);
console.error(compiled.errors[0]);
throw compiled.errors[0];
} else {
var code = compiled.js;
var ast = babelParse(code, {
sourceType: "module",
});
var replaceCode = (node, subCode) =>
code.substring(0, node.start) + subCode + code.substring(node.end);
for (var i = ast.program.body.length - 1; i >= 0; i--) {
var node = ast.program.body[i];
if (node.type === "ImportDeclaration") {
code = replaceCode(
node,
node.specifiers
.map(
(it) =>
`const ${
it.local?.name || it.imported?.name || "*"
} = ___grape__import__('${node.source.value}', '${
it.imported?.name || "*"
}');`
)
.join("\r\n")
);
} else if (node.type === "ExportDefaultDeclaration") {
code = replaceCode(node, `return ${node.declaration.name}`);
}
}
code = `(function(){
${code}
})()`;
var componentStyle = document.createElement("style");
componentStyle.innerHTML = compiled.css;
document.head.appendChild(componentStyle);
app.component(item.name, eval(code));
console.log(item.name);
}
}

View File

@ -0,0 +1,14 @@
import * as vue from "vue";
import ElementPlus from "element-plus";
const libs = {
vue,
'element-plus': ElementPlus
}
window.___grape__import__ = function (lib, name) {
if (Object.prototype.toString.call(libs[lib]) != '[object Module]' && name == '*') {
return libs[lib]
}
return (libs[lib] || {})[name]
}

10
src/store/index.js Normal file
View File

@ -0,0 +1,10 @@
import { defineStore } from "pinia";
export const useCar = defineStore("test", {
state: () => {
return {
title: "云朵技术中台",
menuBar: "left", //菜单栏展示类型 左侧left 头部 top
};
},
});

4
src/style.css Normal file
View File

@ -0,0 +1,4 @@
* {
padding: 0;
margin: 0;
}

13
src/utils/index.js Normal file
View File

@ -0,0 +1,13 @@
import JSEncrypt from 'jsencrypt';
/**
* 加密
* @param {*} value 要加密的字符串
*/
const publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcKv/egiS7sozK3HLWNJTa5UyL1IpI9G145k62OrK8MxgPR4gurGj58Dq8q8E6gMv0EwpylvxHnqciZeta+MJSE1NLD2wmO7oD2w9oN3KMBz6aH2+ESTkH1fhD5Szpw662Gwj/GemIQ+j+p5bX0cS9JMj/zRl+wlGojIl2bIUzFQIDAQAB';
export function encrypt(value) {
if (value == null || value === '') return null;
let encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
return encodeURIComponent(encrypt.encrypt(value));
}

19
src/utils/permission.js Normal file
View File

@ -0,0 +1,19 @@
import tools from '@/utils/tools.js'
let user = tools.data.get('user');
export const hasRole = {
install: (app) => {
app.directive('hasRole', {
mounted(el, binding) {
const value = binding.value
const buttons = user.buttons
console.log(buttons)
if (!buttons.includes(value)) {
el.parentNode.removeChild(el)
}
}
})
}
}

136
src/utils/request.js Normal file
View File

@ -0,0 +1,136 @@
import axios from "axios";
import tools from "@/utils/tools";
// axios.defaults.baseURL = '/api'
axios.defaults.timeout = 10000;
// HTTP request 拦截器
axios.interceptors.request.use(
(config) => {
let user = tools.data.get("user");
if (user) {
config.headers["x-token"] = "Bearer " + user.token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// HTTP response 拦截器
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response) {
if (error.response.status == 401) {
this.$message.error("请重新登录!");
// router.replace({
// path: '/login'
// });
} else if (error.response.status == 404) {
this.$message.error("Status:404正在请求不存在的服务器记录");
} else if (error.response.status == 500) {
this.$message.error({
title: "请求错误",
message: "Status:500服务器发生错误",
});
} else {
this.$message.error(`Status:${error.response.status},未知错误!`);
}
} else {
this.$message.error("请求服务器无响应!");
}
return Promise.reject(error.response);
}
);
var http = {
/** get
* @param {接口地址} url
* @param {请求参数} params
*/
get: function (url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {
params: params,
})
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
/** post
* @param {接口地址} url
* @param {请求参数} params
*/
post: function (url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, params)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
put: function (url, params) {
return new Promise((resolve, reject) => {
axios
.put(url, params)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
delete: function (url, params) {
return new Promise((resolve, reject) => {
axios
.delete(url, params)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
download: function (url) {
let user = tools.data.get("user");
let token = "x-token=" + user.token;
url = url + (url.indexOf("?") > 0 ? "&" : "?") + token;
window.open(url);
},
export: function (url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, params, {
responseType: "blob",
})
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
};
export default http;

36
src/utils/tools.js Normal file
View File

@ -0,0 +1,36 @@
var tool = {}
/* localStorage */
tool.data = {
set(table, settings) {
var _set = JSON.stringify(settings)
return localStorage.setItem(table, _set);
},
get(table) {
var data = localStorage.getItem(table);
try {
data = JSON.parse(data)
} catch (err) {
return null
}
return data;
},
remove(table) {
return localStorage.removeItem(table);
},
clear() {
return localStorage.clear();
}
}
tool.url = function (url, params) {
var hasParams = url.indexOf("?") > 0;
for (var key in params) {
url = url + (hasParams ? '&' : '?') + key + '=' + params[key];
hasParams = true;
}
return url;
}
export default tool

View File

@ -0,0 +1,105 @@
<template>
<div>
<codemirror
v-model="welcomeCode"
placeholder="Code goes here..."
:style="{ height: '400px' }"
:autofocus="true"
:indent-with-tab="true"
:tab-size="2"
:extensions="extensions"
@blur="change()"
/>
<component v-bind:is="currentTabComponent"></component>
</div>
</template>
<script setup>
import { ref } from "vue";
import { Codemirror } from "vue-codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { oneDark } from "@codemirror/theme-one-dark";
import { compileFile } from "@/compiler/sfc-compiler.js";
import { babelParse } from "@vue/compiler-sfc";
import { ElMessage } from "element-plus";
import { table } from "@/compiler/testCode";
// id scopeId css scope
const id = Date.now().toString();
const scopeId = `data-v-${id}`;
const welcomeCode = ref(table);
const extensions = [javascript(), oneDark];
const change = (e) => {
console.log("change", e);
generateComponent(e);
};
const currentTabComponent = ref(eval(""));
const generateComponent = () => {
let compiled = {};
compileFile("TestCode.vue", welcomeCode.value, compiled);
if (compiled.errors.length > 0) {
ElMessage({
type: "error",
duration: 0,
showClose: true,
message: `
发生错误
${compiled.errors[0]}
`,
});
console.error(`发生错误`);
console.error(compiled.errors[0]);
throw compiled.errors[0];
} else {
var code = compiled.js;
var ast = babelParse(code, {
sourceType: "module",
});
var replaceCode = (node, subCode) =>
code.substring(0, node.start) + subCode + code.substring(node.end);
for (var i = ast.program.body.length - 1; i >= 0; i--) {
var node = ast.program.body[i];
if (node.type === "ImportDeclaration") {
code = replaceCode(
node,
node.specifiers
.map(
(it) =>
`const ${
it.local?.name || it.imported?.name || "*"
} = ___grape__import__('${node.source.value}', '${
it.imported?.name || "*"
}');`
)
.join("\r\n")
);
} else if (node.type === "ExportDefaultDeclaration") {
code = replaceCode(node, `return ${node.declaration.name}`);
}
}
code = `(function(){
${code}
})()`;
var componentStyle = document.createElement("style");
componentStyle.innerHTML = compiled.css;
document.head.appendChild(componentStyle);
currentTabComponent.value = eval(code);
// app.component(item.name, eval(code));
// console.log(code);
}
};
generateComponent()
</script>
<style>
* {
padding: 0;
margin: 0;
}
</style>

View File

@ -0,0 +1,12 @@
<script setup>
const props = defineProps({
name: {
type: String,
default: ''
}
})
</script>
<template>
<component :is="props.name"></component>
</template>

View File

@ -0,0 +1,417 @@
<!-- 算法公式 -->
<template>
<a-tabs
default-active-key="1"
@change="tabsChange"
v-if="conArrData.length > 0"
>
<a-tab-pane key="1" title="节点配置">
<div class="dataExtend_content">
<div class="left">
<div>
<span class="add" @click="handleAdd">添加字段</span>
<div class="fieldList">
<div
class="fieldList_item"
v-for="(item, index) in fieldList"
:key="index"
>
<a-input
v-model="item.value"
size="small"
class="fieldList_item_input"
:readonly="item.reactive"
/>
<span v-if="!item.reactive" @click="sure(item)">确定</span>
<div v-else>
<span @click="item.reactive = false">编辑</span>
<span @click="deploy(item)">配置</span>
<span @click="handleDelete(index)">删除</span>
</div>
</div>
</div>
</div>
</div>
</div>
</a-tab-pane>
</a-tabs>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img src="@/assets/imgs/oneNode.f1997e1e.png" alt="" />
<p>请将1个节点连接至本节点</p>
</div>
</div>
<!-- 配置 -->
<a-modal
width="auto"
v-model:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
class="a-modal"
>
<template #title> 函数配置 </template>
<div class="deploy_content">
<div style="width: 1000px">
<codemirror
v-if="codemirrorIf"
:initCode="code"
ref="codemirrorRef"
></codemirror>
</div>
<div class="deploy_deploy">
<div class="left deploy_deploy_box">
<p class="title">参数列表</p>
<ul>
<li
v-for="item in conArrData.length > 0
? conArrData[0].columnList
: []"
@click="clickDeploy(item.ename)"
>
<span>{{ item.name }}</span>
</li>
</ul>
</div>
<div class="right deploy_deploy_box">
<p class="title">函数列表</p>
<div>
<a-collapse :default-active-key="[0]" accordion>
<a-collapse-item
:header="item.title"
:key="index"
v-for="(item, index) in mainList"
>
<ul>
<li
v-for="item2 in item.list"
@click="clickDeploy(item2.value)"
>
{{ item2.label }}
</li>
</ul>
</a-collapse-item>
</a-collapse>
</div>
</div>
</div>
</div>
</a-modal>
</template>
<script setup>
import codemirror from "../../../components/flowEditor/codemirror.vue";
import { ref, reactive, nextTick, toRefs } from "vue";
const props = defineProps({
//
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
const mainList = [
{
title: "基础函数 - 数学函数",
list: [
{
label: "SUM",
value: "${SUM}()",
},
{
label: "ADD",
value: "${ADD}()",
},
{
label: "SUBTRACT",
value: "${SUBTRACT}()",
},
{
label: "MULTIPLY",
value: "${MULTIPLY}()",
},
{
label: "DIVIDE",
value: "${DIVIDE}()",
},
],
},
{
title: "基础函数 - 文字函数",
list: [
{
label: "CONTRACT",
value: "${CONTRACT}()",
},
{
label: "CONCATENAT",
value: "${CONCATENAT}()",
},
{
label: "LEFT",
value: "${LEFT}()",
},
{
label: "RIGHT",
value: "${RIGHT}()",
},
{
label: "LOWER",
value: "${LOWER}()",
},
],
},
{
title: "基础函数 - 逻辑函数",
list: [
{
label: "NUMBERCOMP",
value: "${NUMBERCOMP}()",
},
{
label: "EQ",
value: "${EQ}()",
},
{
label: "IF",
value: "${IF}()",
},
{
label: "ISEMPTY",
value: "${ISEMPTY}()",
},
{
label: "NE",
value: "${NE}()",
},
],
},
{
title: "基础函数 - 集合函数",
list: [
{
label: "INTERSECTI",
value: "${INTERSECTI}()",
},
{
label: "UNIONSET",
value: "${UNIONSET}()",
},
{
label: "DIFFERENCE",
value: "${DIFFERENCE}()",
},
{
label: "SUBSET",
value: "${SUBSET}()",
},
{
label: "ARRAYGET",
value: "${ARRAYGET}()",
},
],
},
];
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const passIf = ref(true);
const getDataStream = () => {
http
.post("/api/metadata/data_stream/exec", {
lineList: lineList.value,
nodeList: conArr.value,
streamId: 1,
rootNodeId: nodeValue.value.id,
})
.then((resp) => {
columnList.value = [];
resp.data.columnList.map((item) => {
columnList.value.push({
title: item.name,
dataIndex: item.keyword,
});
});
columnListData.value = resp.data.dataList;
});
};
const tabsChange = (key) => {
console.log(key);
switch (key) {
case "2":
if (nodeValue.value.columnList) {
getDataStream();
}
break;
default:
break;
}
};
const fieldList = ref([]);
const visible = ref(false);
const codemirrorIf = ref(false);
const handleAdd = () => {
fieldList.value.push({
value: "",
readonly: false,
});
};
const handleDelete = (index) => {
fieldList.value.splice(index, 1);
};
const sure = (item) => {
item.reactive = true;
};
const deploy = () => {
visible.value = true;
nextTick(() => {
codemirrorIf.value = true;
});
};
const handleOk = () => {};
const handleCancel = () => {};
const code = ref("");
const lineListData = ref([]);
const conArrData = ref([]);
const disposeLineList = () => {
console.log("nodeValue-------------", nodeValue.value);
console.log("lineList-------------", lineList.value);
lineListData.value = [];
lineList.value.map((item) => {
if (item.toStr == nodeValue.value.id) {
lineListData.value.push(item);
}
});
console.log(lineListData.value);
lineListData.value.map((lineListDataItem) => {
conArr.value.map((conArrItem) => {
if (lineListDataItem.fromStr == conArrItem.id) {
conArrData.value.push(conArrItem);
}
});
});
console.log("lineListData", lineListData.value);
console.log("conArrData", conArrData.value);
};
disposeLineList();
const codemirrorRef = ref(null);
const clickDeploy = (value) => {
codemirrorRef.value.replaceSelection(value)
};
</script>
<style lang="scss" scoped>
.dataExtend_content {
display: flex;
height: 100%;
margin-left: 10px;
.left {
flex: 0 0 300px;
.add {
color: #3471ff;
font-size: 12px;
display: inline-block;
cursor: pointer;
margin: 0px 6px 8px;
}
.fieldList {
.fieldList_item {
display: flex;
align-items: center;
margin-bottom: 10px;
.fieldList_item_input {
width: 165px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #dcdfe6;
}
span {
margin-left: 5px;
cursor: pointer;
}
}
}
}
.right {
flex: 1;
}
}
.deploy_deploy {
display: flex;
font-size: 0;
.left {
flex: 0 0 300px;
margin-right: 15px;
ul {
list-style-type: none;
margin: 0;
padding: 0;
height: 300px;
li {
font-size: 12px;
padding: 6px 16px;
cursor: pointer;
transition: all 0.4s;
&:hover {
background-color: rgba(126, 134, 142, 0.08);
}
}
}
}
.right {
flex: auto;
ul {
list-style-type: none;
margin: 0;
padding: 0;
li {
padding: 3px 0;
cursor: pointer;
transition: all 0.4s;
&:hover {
color: #165dff;
}
}
}
}
.deploy_deploy_box {
border: 1px solid rgba(17, 31, 44, 0.08);
border-radius: 8px;
.title {
padding: 8px 16px;
font-size: 12px;
margin: 0;
border-bottom: 1px solid rgba(17, 31, 44, 0.08);
}
}
}
.a-modal .arco-modal-body {
box-sizing: border-box;
}
.cm-component {
box-sizing: border-box;
}
</style>

View File

@ -0,0 +1,348 @@
<!-- 字段设置 -->
<template>
<a-tabs
default-active-key="1"
@change="tabsChange"
v-if="conArrData.length > 0"
>
<a-tab-pane key="1" title="字段配置">
<draggable
v-model="data.fieldSettingList"
class="fun-classify"
@end="() => {}"
@start="(e) => move(e, 'baseArray')"
item-key="id"
>
<template #item="{ element }">
<div class="fun-item" :data-type="element.type">
<div class="field-item-head">
<span>{{ element.name }}</span>
<div class="head-action" @click="clickSet(element)">
<icon-settings />
</div>
</div>
<div class="field-item-cell">
<div class="type-item">
<span>类型</span>
<div class="type-item-content">
<span class="select-option-type string">数据类型</span>
</div>
</div>
</div>
</div>
</template>
</draggable>
</a-tab-pane>
</a-tabs>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img src="@/assets/imgs/oneNode.f1997e1e.png" alt="" />
<p>请将1个节点连接至本节点</p>
</div>
</div>
<a-modal
width="auto"
v-model:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
>
<template #title> 字段设置 </template>
<a-form :model="form" :style="{ width: '500px' }" label-align="left">
<a-form-item field="name" label="字段名:">
<a-input v-model="form.name" placeholder="请输入字段名" />
</a-form-item>
<a-form-item field="type" label="类型:">
<a-select placeholder="请选择数据类型">
<a-option>字符串</a-option>
</a-select>
</a-form-item>
<a-form-item field="type" label="值替换:">
<a-button type="outline" @click="addList">新增替换</a-button>
</a-form-item>
</a-form>
<a-form
:model="form"
:style="{ width: '500px' }"
label-align="left"
layout="vertical"
>
<a-row
class="replace-item"
:gutter="12"
v-for="(item, index) in form.list"
:key="index"
>
<a-col :span="7">
<a-form-item label="替换类型:">
<a-select placeholder="未选择" v-model="item.type">
<a-option value="null">空值</a-option>
<a-option value="custom">自定义值</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="7" v-if="item.type == 'custom'">
<a-form-item field="name" label="原来值:">
<a-input
placeholder="请输入原来的值"
allow-clear
v-model="item.oldVal"
/>
</a-form-item>
</a-col>
<a-col :span="7" v-if="item.type == 'custom' || item.type == 'null'">
<a-form-item field="name" label="替换值:">
<a-input
placeholder="请输入替换值"
allow-clear
v-model="item.newVal"
/>
</a-form-item>
</a-col>
<a-col :span="3">
<a-form-item>
<div class="del" @click="clickDel(index)"><icon-delete /></div>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script setup>
import { ref, reactive, nextTick, toRefs } from "vue";
import draggable from "vuedraggable";
import { IconSettings, IconDelete } from "@arco-design/web-vue/es/icon";
import http from "@/scripts/request";
const data = reactive({
fieldSettingList: [],
});
const props = defineProps({
//
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const lineListData = ref([]);
const conArrData = ref([]);
const disposeLineList = () => {
lineListData.value = [];
lineList.value.map((item) => {
if (item.toStr == nodeValue.value.id) {
lineListData.value.push(item);
}
});
console.log(lineListData.value);
lineListData.value.map((lineListDataItem) => {
conArr.value.map((conArrItem) => {
if (lineListDataItem.fromStr == conArrItem.id) {
conArrData.value.push(conArrItem);
let columnList = JSON.parse(JSON.stringify(conArrItem.columnList));
columnList.map((item) => {
item.nodeId = nodeValue.value.id;
});
data.fieldSettingList = JSON.parse(JSON.stringify(columnList));
nodeValue.value.columnList = columnList;
}
});
});
console.log("lineListData", lineListData.value);
console.log("conArrData", conArrData.value);
};
disposeLineList();
const move = () => {};
//tab
const tabsChange = () => {};
//
const visible = ref(false);
const form = reactive({
name: "",
type: "",
id: "",
list: [],
});
const clickSet = (el) => {
console.log(nodeValue.value);
form.name = el.name;
form.id = el.keyword;
form.list = [];
let nodeValueData = nodeValue.value.data
? JSON.parse(nodeValue.value.data)
: [];
console.log("nodeValue", nodeValue.value.data, nodeValueData);
if (nodeValueData instanceof Array) {
nodeValueData.map((item) => {
if (item.columnName == el.keyword) {
form.list.push(item);
}
});
}
visible.value = true;
};
const handleOk = () => {
console.log(form);
let dataList = [];
form.list.map((item) => {
console.log(item);
dataList.push({
columnName: form.id,
type: item.type,
oldVal: item.oldVal,
newVal: item.newVal,
});
});
console.log("dataList", form);
console.log(nodeValue.value);
let nodeValueData = [];
if (nodeValue.value.data !== "") {
nodeValueData = JSON.parse(nodeValue.value.data);
}
nodeValueData.map((item) => {
if (item.columnName != form.id) {
dataList.push(item);
}
});
console.log("dataList", dataList);
nodeValue.value.data = JSON.stringify(dataList);
};
const handleCancel = () => {};
//
const addList = () => {
form.list.push({
type: "",
oldVal: "",
newVal: "",
});
};
const clickDel = (index) => {
form.list.splice(index, 1);
};
// const getMetadataColumn = () => {
// http.get("/api/metadata/dict", {}).then((resp) => {
// console.log(resp);
// });
// };
// getMetadataColumn();
</script>
<style lang="scss" scoped>
:deep(.arco-tabs-content) {
padding-top: 0;
}
.fun-classify {
height: 360px;
}
.fun-item {
width: 180px;
height: calc(100% - 2px);
position: relative;
display: inline-flex;
flex-direction: column;
border-color: #e9e9e9;
border-style: solid;
border-width: 1px 1px 1px 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: move;
.field-item-head {
border-color: #e9e9e9;
border-style: solid;
border-width: 0 0 1px 0;
height: 38px;
display: flex;
align-items: center;
padding: 0 10px;
box-sizing: border-box;
justify-content: space-between;
position: relative;
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
min-width: 115px;
}
.head-action {
cursor: pointer;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
}
.field-item-cell {
padding: 5px;
box-sizing: border-box;
.type-item {
display: flex;
align-items: center;
height: 25px;
span {
font-size: 12px;
color: #333;
}
.type-item-content {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
.string {
color: #1485f6;
background: rgba(20, 133, 246, 0.15);
}
.select-option-type {
float: left;
flex: none;
min-width: 44px;
height: 18px;
padding: 0 10px;
line-height: 18px;
text-align: center;
border-radius: 40px;
box-sizing: border-box;
font-weight: 500;
}
}
}
}
}
.replace-item {
position: relative;
.del {
cursor: pointer;
margin-top: 26px;
}
}
</style>

View File

@ -0,0 +1,293 @@
<!-- 数据筛选 -->
<template>
<a-tabs
default-active-key="1"
@change="tabsChange"
v-if="conArrData.length > 0"
>
<a-tab-pane key="1" title="节点配置">
<div class="data-filter-box">
<div>
<span>筛选出符合以下</span>
<a-select
:style="{ width: '80px', margin: '0 10px' }"
placeholder="Select"
:trigger-props="{ autoFitPopupMinWidth: true }"
v-model="data.type"
>
<a-option value="and">所有</a-option>
<a-option value="or">任一</a-option>
</a-select>
<span>条件的数据</span>
</div>
<div class="data-filter-add">
<a-dropdown @select="handleSelect" position="bl">
<a-button type="primary" size="small">
<template #icon>
<icon-plus />
</template>
<template #default>添加过滤条件</template>
</a-button>
<template #content>
<a-doption
v-for="item in columnList"
:key="item.id"
:value="item.keyword"
>{{ item.name }}</a-doption
>
</template>
</a-dropdown>
</div>
<div class="filter-add-list">
<div
class="filter-add-list-box"
v-for="(item, index) in data.conditionList"
:key="index"
>
<a-input
class="item"
:style="{ width: '200px' }"
v-model="item.columnText"
placeholder="Please enter something"
readonly
/>
<a-select
class="item"
v-model="item.condition"
:style="{ width: '150px', marginRight: '15px' }"
placeholder="请选择条件"
:trigger-props="{ autoFitPopupMinWidth: true }"
@change="update"
>
<a-option value="eq">等于</a-option>
<a-option value="ne">不等于</a-option>
</a-select>
<div>
<a-input
class="item"
:style="{ width: '200px' }"
v-model="item.val"
placeholder="请输入筛选条件"
allow-clear
@blur="update"
/>
<!-- <a-tag
v-for="(tag, index) of tags"
:key="tag"
:closable="index !== 0"
@close="handleRemove(tag)"
>
{{ tag }}
</a-tag>
<a-input
v-if="showInput"
ref="inputRef"
:style="{ width: '90px' }"
size="mini"
v-model.trim="inputVal"
@keyup.enter="handleAdd"
@blur="handleAdd"
/>
<a-tag
v-else
:style="{
width: '90px',
backgroundColor: 'var(--color-fill-2)',
border: '1px dashed var(--color-fill-3)',
cursor: 'pointer',
}"
@click="handleEdit"
>
<template #icon>
<icon-plus />
</template>
添加
</a-tag> -->
</div>
<div class="del" @click="clickDel(index)"><icon-delete /></div>
</div>
</div>
</div>
</a-tab-pane>
</a-tabs>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img src="@/assets/imgs/oneNode.f1997e1e.png" alt="" />
<p>请将1个节点连接至本节点</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, nextTick, toRefs } from "vue";
import { IconPlus, IconDelete } from "@arco-design/web-vue/es/icon";
const data = reactive({
type: "and",
conditionList: [],
});
const columnList = ref([]);
const props = defineProps({
//
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const lineListData = ref([]);
const conArrData = ref([]);
const disposeLineList = () => {
console.log("nodeValue-------------", nodeValue.value);
console.log("lineList-------------", lineList.value);
lineListData.value = [];
lineList.value.map((item) => {
if (item.toStr == nodeValue.value.id) {
lineListData.value.push(item);
}
});
console.log(lineListData.value);
lineListData.value.map((lineListDataItem) => {
conArr.value.map((conArrItem) => {
if (lineListDataItem.fromStr == conArrItem.id) {
conArrData.value.push(conArrItem);
columnList.value = JSON.parse(JSON.stringify(conArrItem.columnList));
columnList.value.map(item=>{
item.nodeId = nodeValue.value.id
})
nodeValue.value.columnList = JSON.parse(
JSON.stringify(columnList.value)
);
if (nodeValue.value.data) {
let nodeValueData = JSON.parse(nodeValue.value.data);
data.type = nodeValueData.type;
data.conditionList = nodeValueData.conditionList;
}
}
});
});
console.log("lineListData", lineListData.value);
console.log("conArrData", conArrData.value);
};
disposeLineList();
//tab
const tabsChange = () => {};
// tab
const tags = ref(["Tag 1", "Tag 2", "Tag 3"]);
const inputRef = ref(null);
const showInput = ref(false);
const inputVal = ref("");
const handleEdit = () => {
showInput.value = true;
nextTick(() => {
if (inputRef.value) {
inputRef.value.focus();
}
});
};
const handleAdd = () => {
if (inputVal.value) {
tags.value.push(inputVal.value);
inputVal.value = "";
}
showInput.value = false;
};
const handleRemove = (key) => {
tags.value = tags.value.filter((tag) => tag !== key);
};
// end tab
const handleSelect = (val) => {
columnList.value.map((item) => {
if (item.keyword == val) {
data.conditionList.push({
columnText: item.name,
columnName: item.keyword,
condition: "",
val: "",
});
}
});
};
//
const update = () => {
let nodeValueDate = {
type: data.type,
conditionList: data.conditionList,
};
nodeValue.value.data = JSON.stringify(nodeValueDate);
};
const clickDel = (index)=>{
data.conditionList.splice(index,1)
}
</script>
<style lang="scss" scoped>
.data-filter-box {
padding: 0 16px;
.data-filter-add {
margin: 6px 0;
}
}
.filter-add-list {
.filter-add-list-box {
display: flex;
align-items: center;
margin-bottom: 10px;
.item {
margin-right: 15px;
}
.item-tag {
display: flex;
align-items: center;
border: 1px solid #efefef;
padding: 3px 3px 0;
border-radius: 4px;
max-width: 300px;
flex-wrap: wrap;
span {
margin-left: 5px;
margin-bottom: 3px;
}
}
.del {
cursor: pointer;
margin-left: 15px;
}
}
}
</style>

View File

@ -0,0 +1,334 @@
<template>
<a-tabs
default-active-key="1"
@change="tabsChange"
v-if="conArrData.length > 0"
>
<a-tab-pane key="1" title="节点配置">
<div class="grouping-content">
<div class="grouping-content-left">
<div class="group-title"><span class="label">分组字段</span></div>
<a-dropdown @select="handleSelect" position="bl">
<a-button type="primary" size="small">
<template #icon>
<icon-plus />
</template>
<template #default>添加分组字段</template>
</a-button>
<template #content>
<a-doption
v-for="item in columnList"
:key="item.id"
:value="item.keyword"
>{{ item.name }}</a-doption
>
</template>
</a-dropdown>
<div class="grouping-list">
<div class="grouping-list-item" v-for="item in leftGroupingList">
<span class="name">{{ item.name }}</span>
<div class="del" @click="clickLeftDel(index)">
<icon-delete />
</div>
</div>
</div>
</div>
<div class="grouping-content-right">
<div class="group-title">
<span class="label">汇总字段</span>
<div>
<span>是否保留原字段</span>
<a-switch v-model="nodeValueData.isRetain" @change="initData">
<template #checked> </template>
<template #unchecked> </template>
</a-switch>
</div>
</div>
<a-dropdown @select="addSummary" position="bl">
<a-button type="primary" size="small">
<template #icon>
<icon-plus />
</template>
<template #default>添加汇总字段</template>
</a-button>
<template #content>
<a-doption
v-for="item in columnList"
:key="item.id"
:value="item.keyword"
>{{ item.name }}</a-doption
>
</template>
</a-dropdown>
<div class="grouping-list">
<div class="grouping-list-item" v-for="item in rightGroupingList">
<span class="name">{{ item.name }}</span>
<a-select
:style="{ width: '160px' }"
v-model="item.type"
placeholder="请选择汇总方式"
@change="initData()"
>
<a-option value="count">总数</a-option>
</a-select>
<div class="del" @click="clickRightDel(index)">
<icon-delete />
</div>
</div>
</div>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="2" title="查看数据">
<a-table
:columns="dataColumnList"
:data="columnListData"
:pagination="false"
size="small"
/>
</a-tab-pane>
</a-tabs>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img src="@/assets/imgs/oneNode.f1997e1e.png" alt="" />
<p>请将1个节点连接至本节点</p>
</div>
</div>
</template>
<script setup >
import http from "@/scripts/request";
import { ref, reactive, nextTick, toRefs } from "vue";
import { IconPlus, IconDelete } from "@arco-design/web-vue/es/icon";
const columnList = ref([]);
const leftGroupingList = ref([]); //left
const rightGroupingList = ref([]); //left
let nodeValueData = ref({
isRetain: false,
groupColumnNameList: [],
collectColumnList: [],
});
const props = defineProps({
//
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const lineListData = ref([]);
const conArrData = ref([]);
const disposeLineList = () => {
console.log("nodeValue-------------", nodeValue.value);
console.log("lineList-------------", lineList.value);
lineListData.value = [];
lineList.value.map((item) => {
if (item.toStr == nodeValue.value.id) {
lineListData.value.push(item);
}
});
console.log(lineListData.value);
lineListData.value.map((lineListDataItem) => {
conArr.value.map((conArrItem) => {
if (lineListDataItem.fromStr == conArrItem.id) {
conArrData.value.push(conArrItem);
columnList.value = JSON.parse(JSON.stringify(conArrItem.columnList));
columnList.value.map((item) => {
item.nodeId = nodeValue.value.id;
});
nodeValue.value.columnList = JSON.parse(
JSON.stringify(columnList.value)
);
console.log(nodeValue.value.data);
if (nodeValue.value.data) {
nodeValueData.value = JSON.parse(nodeValue.value.data);
leftGroupingList.value = [];
nodeValueData.value.groupColumnNameList.map((item) => {
columnList.value.map((groupColumnNameListItem) => {
if (groupColumnNameListItem.keyword == item) {
leftGroupingList.value.push(groupColumnNameListItem);
}
});
});
rightGroupingList.value = [];
nodeValueData.value.collectColumnList.map((item) => {
columnList.value.map((columnListItem) => {
if (columnListItem.keyword == item.columnName) {
rightGroupingList.value.push({
name: columnListItem.name,
keyword: columnListItem.keyword,
type: item.type,
});
}
});
});
} else {
console.log("不存在");
nodeValueData.value = {
groupColumnNameList: [],
collectColumnList: [],
isRetain: false,
};
}
}
});
});
console.log("lineListData", lineListData.value);
console.log("conArrData", conArrData.value);
};
disposeLineList();
const tabsChange = (key) => {
console.log(key);
switch (key) {
case "2":
if (nodeValue.value.columnList) {
getDataStream();
}
break;
default:
break;
}
};
// left
const handleSelect = (keyword) => {
columnList.value.map((item) => {
if (item.keyword == keyword) {
leftGroupingList.value.push(item);
}
});
initData();
};
const clickLeftDel = (index) => {
leftGroupingList.value.splice(index, 1);
initData();
};
const clickRightDel = (index) => {
rightGroupingList.value.splice(index, 1);
initData();
};
const addSummary = (keyword) => {
columnList.value.map((item) => {
if (item.keyword == keyword) {
rightGroupingList.value.push({
name: item.name,
keyword: item.keyword,
type: "",
});
}
});
initData();
};
//
const initData = () => {
nodeValueData.value.groupColumnNameList = [];
leftGroupingList.value.map((item) => {
nodeValueData.value.groupColumnNameList.push(item.keyword);
});
nodeValueData.value.collectColumnList = [];
rightGroupingList.value.map((item) => {
nodeValueData.value.collectColumnList.push({
columnName: item.keyword,
type: item.type,
});
});
nodeValue.value.data = JSON.stringify(nodeValueData.value);
};
//
const dataColumnList = ref([])
const columnListData = ref([])
const getDataStream = () => {
http
.post("/api/metadata/data_stream/exec", {
lineList: lineList.value,
nodeList: conArr.value,
streamId: 1,
rootNodeId: nodeValue.value.id,
})
.then((resp) => {
dataColumnList.value = [];
resp.data.columnList.map((item) => {
dataColumnList.value.push({
title: item.name,
dataIndex: item.keyword,
});
});
columnListData.value = resp.data.dataList;
});
};
</script>
<style lang="scss" scoped>
.grouping-content {
display: flex;
.grouping-content-left {
flex: 0 0 260px;
border-right: 1px solid #e9e9e9;
padding: 0 16px;
box-sizing: border-box;
}
.group-title {
font-size: 14px;
display: flex;
align-items: center;
margin-bottom: 10px;
.label {
margin-right: 15px;
}
}
.grouping-content-right {
flex: auto;
padding: 0 16px;
box-sizing: border-box;
}
.grouping-list {
.grouping-list-item {
font-size: 14px;
margin-top: 10px;
display: flex;
align-items: center;
.name {
flex: 0 0 200px;
}
.del {
flex: 0 0 20px;
cursor: pointer;
margin-left: 20px;
}
}
}
}
</style>

View File

@ -0,0 +1,243 @@
<!-- 数据连接 -->
<template>
<a-tabs default-active-key="1" @change="tabsChange" v-if="passIf">
<a-tab-pane key="1" :title="nodeValue.name">
<div id="join_content">
<div class="left">
<h4>1.设置连接方式</h4>
<a-radio-group v-model="data.joinType" :options="options" />
</div>
<div class="right">
<h4>2.添加连接方式</h4>
<div class="content">
<div>
<h5>左侧表单</h5>
<a-select
v-model="data.leftColumnName"
:style="{ width: '220px' }"
placeholder="请选择左侧表单字段"
@change="setData"
>
<a-option
v-for="item in conArrData[0].columnList"
:value="item.keyword"
>{{ item.name }}</a-option
>
</a-select>
</div>
<div>
<h5>右侧表单</h5>
<a-select
v-model="data.rightColumnName"
:style="{ width: '220px' }"
placeholder="请选择右侧表单字段"
@change="setData"
>
<a-option
v-for="item in conArrData[1].columnList"
:value="item.keyword"
>{{ item.name }}</a-option
>
</a-select>
</div>
</div>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="2" title="查看数据">
<a-table
:columns="columnList"
:data="columnListData"
:pagination="false"
size="small"
/>
</a-tab-pane>
</a-tabs>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcAAAACQCAMAAACoAksCAAAB71BMVEUAAACMsdufveF1oNJ1oNF2p9h3o9N0odN7pNR4o9N7pdR0odN4otL/eXf/eHb/eXf/e3v/eXb/enf/eXf/eXb/fnh1oNL/e3mZud95o9P/jIzkoqr8kZL/eXf/hYX/eXh2odL/enj/eniFrub/eXj/eXj/end2oNP/enj/enj/fHn/fn5/qtyGrNmBqNf/e3n/eHb/eXd1odN3odT8lpf9jIz/e3n/eHf/eXf/eXf/eXd4odV5otZ4odf+gX93odN3odP/enl3otN2odN2oNL/eXf/eHZ1oNL/eXf/eXf/eXj/gID7oKH/eXf/eXZ1oNN1oNJ0oNL/eXd3odL/fn78m5v/eHZ3o9P/eHZ1oNN2oNL4fH3jhIzYhpOykKt4otKNnMTy+P/N4v50n9H/eHauyOfr9P3l8P/c6ffw9//Y6P/O4/+sxubq8//T5v/d6//P5P/05Oro8v79hoXt9f/z7/b20td6o9Ty9Pv4ur3+gIDf6/iwyuidvOF/p9b3xMj+e3rQ4PO50Ov03uSkweOStd2MsNr2ys7y9/74vsH5sLP6pqjW6P/S5f/l7vvV4/Xz5+62zurj7//f7f/a6v/I2vHC1u690uynxOX12t/5trn7qavh7PrZ5/f4t7r5rK60vtu4utXIqr/Ro7TokJcHc7DKAAAAYXRSTlMA+P2fWRo6f/nF+Vy99pm4NMRa18se/Sj9+wYC/v4JSvVEOwask3JOTjcxEw39+fCznWZE/v38wI+EaiwfFvvp4t7WtKilo5GMeGQN/urQm31xVD4Z++PCfnNb+u7n1cjEP8ysLwAAB8xJREFUeNrs3XdTE0EYx/EH+6hBgiQhhIBUEVHE3nvvvbfncopHQCGFqhRBQbD33l+oe7k1CTjqBBeze3k+zsjPG//7GgjJuYCp7NiU9Ju/Hsi4+I7MC3hnpJvXHdi2AUjqfLO9rx5i+l3ty5q3DkjKzni7UA5G/tYyICmqmfcKZfHQOwtIipYFZPj8yWVNBpKiKV6Ux+vZQFI0aybKo5ECUsBMQwEVRwEVRwEVRwEVRwEVRwEVRwEVRwEVRwEV90vApuvBZA+eYhLjeXNQoOYGgwIKDmgEtTGaMOG5JlgDBRQcsF4b6wYmPNAEC2JctKOwd+WW3S4gExewWRMNf2rtDL9tbe1Y4dgLRMGA/b0jUWTa3/pzgSgXsH3FCHIdtceBqBbwXmcUuaEVdUBUC7h8GON6avflxABAUY7lONsuvn0ABXy6ki4XjL3sKoIMIEnA8CDG9evFegwAeHTLGrZ38M26uPhcDQDZfC8EWMhnNgCsZh+dawvA7iQJ2DkRAZm5C8DmJAlY+BbjBh0Lc2MAYF+upZrtUr59AEVJlxfwbX4K5dOsVp27NvuArjvsXlCSgB/D7fjT8nIQpGiNXm73T6KSBIyGh5HrcXhAmFIf2JwkAbHV3zEUG4MrK4GoFxB7egsHW1paT+i7fSCUbyHYmTQBsWWkU9cdc0pBrLraJWBnYwNGtLFCmNCgCXYfkwx9vlwAoq3SD4Kd8YAJT4NasuCNUXnva0Jdj0z4+4Fz9LlgZzxgMiMZjmEIhUgB6Z4YCigNCkgBKWAKlAi4e9VOsDPbB7Q7Cqg4Cqg42wcsXnsU7Mz2ASfiWWjZumnpt54CjlPNrnkBGWyeD8zZGSgPJQJO3bz4ZdfVtAs9uuKeHDtqK4TSyJ8MIH3A2VlXUQ6P3LMANrhfoCyuepfKH3C+W5bTHRGfzCsD2LVYmofgwOYaANkDHspCabwPHAMo27q4D2XwMN99DABkD7jtNcrDOwUAag4FvFnpNzOwdR1MgJziUhBpdiPKY+YsMK1benh62h1ZpsZxvTIGJBQwc1DA/2znyVUUUGXsWSgFVBkFVBwFVBwFVBwFVBwFVFxFXnn6AobuXxepIZKBAZm0BWzSBGs2KOC4FTn37Esx4DNNtBAFHDeXvrJ2T1FKAW9ootVnXsCS3CpRAfvvbaytpIB/J+ezUJfegu0dG899a8dRXgzcunVr0kvEvth4ZVBAeQMiS9gZvtee3C+w6O7du4sCT/oC3bHRSAFlDogYHZ3w1iI05d9p7DbMMdBNAeUOaCXsYQl5OfabYXQ96npkmAWvtFFAwQEdeTEeAFhlzQo2c/Ms1ebfsWY2m1X88gK2l1hzJ5vFeXkHzYBc9GNvOJoIeCXA3aGAID4glwMADmvOYbNSt5SyfcCaTjb36pZitrdY86CZ2xyJgK2FvZ+GEgG7bt++/YT9ut1HARnhj0BnjMf8gzVXs3nUaalme4k1K9is4pcXsF1uzTo2S5zOPL0lke/dY3PwgKbGO1fuWoO+BopVWlUi7Gsgz+dn+eKeBF7fvHmzsW3gZWDAHN35FFBOLKCVb4R9TIgMdLe1tXXfCmGjOdryH1JAObGAZr4LX+iVGDW59J5C/w4PvZT2n2XPXSIqoL/cA5BKwHpNtEjmBRT2jbyv0pPyG7oNmljPMvAN3fTeUhFpEsnIxFsq6J4YxVFAxVFAxVFAxbFvIyggoYC2QQEVJ2XAsg1T064GJkbp0RK7B5x2yh2QwOYjPrDQs9CUAh52T3r54Vra9d30XtoAABQwlYBnAZZJc1ja+0XbAaQPuD0fpXHVPR98m+T5F/XBvQxA9oBnZhooiz73VJgWkOfwPczaBSB7wA2bbqIkIosmA0zxojxebwOQPSDMdzdGUAZdWZtq6Mjl8RTc5M2alH6L3bOnZsKZ2SW5xSBYzdJdk9Pu0PRlmXHoud1RQMVRQMXZPqB5zIid2T4g/fzA36OAEqCAiqOAiqOAivsloHGjIdnzJkxW3yDUUwooOqDxQBstGMGEZ5pgNyY8YN1JJ9jZ2IChP/6PGFZXrGZMeDP8/Xx2lQ/IvwSs/+ODpFkTDX96vNy/fHj4xMq51UAUDBgNr+iPdSx05ABRL+D+wnZrDO2fWwDCeFYtAHuTJOBgbwty0Y2nxfXbovtt/oCWJOD+EYzr2AJCFOVUOHS9AuxNkoDhHox7o5c4LGsBYIk1D7BZzS9Xsr2DbzY9fJ5mu47vIgCXbtpt92e1kgTsHMS4fr1Kt1QmzjozS+Xolj1sl/NtBuRzDds7+eYBnSVgd5IEXHEP41r9nmxLCQBUWrOCTRe/XMz2Wr7ZPM5nFdt7+S4AWLhmrwfsT5KAn5Zj3Dt7v3Riz4AtvT3IvfEXA1EtIN7rfMNThuuAqBdwaEQffmyeZrzSuQ+IegERe8J650a9dk8BkH8I2KSN9RQT7muCPcAk/V8vllK+cQfknmuj3R+VN6gJFQzRfaGiA2JTfbImHMUI1QsUMujGXronJsNRQMVRQMVRQMVRQMVRQMVRQMVRQMVRQMX92GgEDnEwGoFDHKiLDY5jh8CAyYVhFJAIbJL7EwcN0AtgGAWkAh7GQXP6XpOCDcMoIBWwKkgUJQ4KMF3MiGEUkA6snSQlclIHHDDpKYzGH3lA2cglnHPAgTP7aPlJBgAAYcggW3jZ838AAAAASUVORK5CYII="
/>
<p>请将2个节点连接至本节点</p>
</div>
</div>
</template>
<script setup>
import http from "@/scripts/request";
import { reactive, toRefs, watch, ref } from "vue";
const data = ref({
joinType: "inner",
rightNodeId: "",
rightColumnName: "",
leftNodeId: "",
leftColumnName: "",
});
const lineListData = ref([]); //
const conArrData = ref([]);
const options = [
{ label: "内连接", value: "inner" },
{ label: "左连接", value: "left" },
{ label: "右连接", value: "right" },
];
const columnList = ref([]);
const columnListData = ref([]);
const passIf = ref(false);
const props = defineProps({
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const disposeLineList = () => {
console.log("nodeValue-------------", nodeValue.value);
console.log("lineList-------------", lineList.value);
lineListData.value = [];
conArrData.value = [];
lineList.value.map((item) => {
if (item.toStr == nodeValue.value.id) {
lineListData.value.push(item);
}
});
console.log(lineListData.value);
lineListData.value.map((lineListDataItem) => {
conArr.value.map((conArrItem) => {
if (lineListDataItem.fromStr == conArrItem.id) {
conArrData.value.push(JSON.parse(JSON.stringify(conArrItem)));
}
});
});
console.log("conArrData", conArrData.value);
if (conArrData.value.length == 2) {
data.value.leftNodeId = conArrData.value[0].id;
data.value.rightNodeId = conArrData.value[1].id;
passIf.value = true;
// if (nodeValue.value.data) {
// data.value = JSON.parse(nodeValue.value.data);
// }
} else {
passIf.value = false;
}
};
disposeLineList();
watch(
() => lineList.value.length,
(newVal, oldVal) => {
disposeLineList();
}
);
const setData = () => {
conArr.value.map((item) => {
console.log(item.id);
console.log(nodeValue.value.id);
if (item.id == nodeValue.value.id) {
item.data = JSON.stringify(data.value);
conArrData.value.map((item) => {
item.columnList.map((item2) => {
item2.nodeId = nodeValue.value.id;
});
});
item.columnList = [
...conArrData.value[0].columnList,
...conArrData.value[1].columnList,
];
}
});
console.log(conArr.value);
};
const getDataStream = () => {
http
.post("/api/metadata/data_stream/exec", {
lineList: lineList.value,
nodeList: conArr.value,
streamId: 1,
rootNodeId: nodeValue.value.id,
})
.then((resp) => {
columnList.value = [];
resp.data.columnList.map((item) => {
columnList.value.push({
title: item.name,
dataIndex: item.keyword,
});
});
columnListData.value = resp.data.dataList;
});
};
const tabsChange = (key) => {
console.log(key);
switch (key) {
case "2":
if (nodeValue.value.columnList) {
getDataStream();
}
break;
default:
break;
}
};
</script>
<style lang="scss">
#join_content {
flex: 1;
display: flex;
padding: 0px 16px;
.left {
flex: 0 0 300px;
}
.right {
flex: auto;
.content {
display: flex;
& > div {
flex: 1;
margin-left: 16px;
}
}
}
}
.dissatisfy {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
margin-top: 40px;
.dissatisfy_box {
max-width: 300px;
text-align: center;
img {
width: 100%;
}
p {
font-size: 14px;
color: red;
}
}
}
</style>

View File

@ -0,0 +1,109 @@
<template>
<div id="output" v-if="LinkedDataIf">
<a-table
:columns="columnList"
:data="columnListData"
:pagination="false"
size="small"
/>
</div>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img src="@/assets/imgs/oneNode.f1997e1e.png" alt="" />
<p>{{ LinkedDataText }}</p>
</div>
</div>
</template>
<script setup>
import { ref, toRefs } from "vue";
import http from "@/scripts/request";
const props = defineProps({
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const columnList = ref([]);
const columnListData = ref([]);
const getDataStream = () => {
http
.post("/api/metadata/data_stream/exec", {
lineList: lineList.value,
nodeList: conArr.value,
streamId: 1,
rootNodeId: nodeValue.value.id,
})
.then((resp) => {
columnList.value = [];
resp.data.columnList.map((item) => {
columnList.value.push({
title: item.name,
dataIndex: item.keyword,
});
});
columnListData.value = resp.data.dataList;
});
};
const LinkedDataIf = ref(false);
const LinkedDataText = ref("请将1个节点连接至本节点");
const verify = () => {
console.log(lineList.value);
let LinkedData = {};
LinkedDataIf.value = false;
lineList.value.map((item) => {
console.log(item);
if (item.toStr == nodeValue.value.id) {
conArr.value.map((item2) => {
if (item.fromStr == item2.id) {
LinkedDataIf.value = true;
LinkedData = JSON.parse(JSON.stringify(item2));
}
});
}
});
if (!LinkedDataIf.value) {
LinkedDataText.value = "请将1个节点连接至本节点";
return false;
}
console.log(LinkedData.columnList);
if (LinkedData.columnList) {
LinkedData.columnList.map((item) => {
item.nodeId = nodeValue.value.id;
});
conArr.value.map((item) => {
if (item.id == nodeValue.value.id) {
item.columnList = LinkedData.columnList;
}
});
console.log("conArr", conArr.value);
getDataStream();
} else {
LinkedDataText.value = "请设置前置数据";
LinkedDataIf.value = false;
}
};
verify();
</script>
<style lang="scss">
#output {
flex: 1;
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div class="disposition_header">
<h2>{{ nodeValue.name }}</h2>
</div>
<div style="display: flex">
<div class="disposition_left">
<div class="title">
<h4>输入源</h4>
<span @click="clickChange">更改输入源</span>
</div>
<ul>
<li v-for="item in columnList">
<span>{{ item.title }}</span>
<span
class="type"
:style="{
backgroundColor: item.typeColor,
}"
>{{ item.typeStr }}</span
>
</li>
</ul>
</div>
<div class="disposition_right">
<a-table
:columns="columnList"
:data="columnListData"
:pagination="false"
size="small"
/>
</div>
</div>
</template>
<script setup>
import { toRefs } from "vue";
const emit = defineEmits(["alertTable"]);
const props = defineProps({
//
columnList: {
type: Array,
default: () => [],
},
nodeValue: {
type: Object,
default: () => [],
},
columnListData: {
type: Array,
default: () => [],
},
});
//使
const { columnList, nodeValue, columnListData } = toRefs(props);
const clickChange = () => {
console.log(nodeValue.value);
emit("alertTable");
};
</script>
<style lang="scss">
.disposition_left {
flex: 0 0 300px;
padding: 16px;
box-sizing: border-box;
.title {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
h4 {
font-size: 14px;
padding: 0;
margin: 0;
}
span {
color: #0db3a6;
cursor: pointer;
}
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
li {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 0 5px;
font-size: 14px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.type {
padding: 3px 6px;
color: #ffffff;
border-radius: 12px;
font-size: 12px;
}
}
}
}
.disposition_right {
flex: 1;
}
.disposition_header {
padding: 8px 16px;
border-bottom: 1px solid #e9e9e9;
h2 {
font-size: 16px;
margin: 0;
padding: 0;
}
}
.dissatisfy {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
.dissatisfy_box {
max-width: 300px;
text-align: center;
img {
width: 100%;
}
p {
font-size: 14px;
color: red;
}
}
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<a-tabs
default-active-key="1"
@change="tabsChange"
v-if="conArrData.length == 0"
>
<a-tab-pane key="1" title="节点配置">
<div class="grouping-content">
<div class="grouping-content-left">
<div class="group-title"><span class="label">结构解析字段</span></div>
<a-dropdown @select="handleSelect" position="bl">
<a-button type="primary" size="small">
<template #icon>
<icon-plus />
</template>
<template #default>添加结构解析字段</template>
</a-button>
<template #content>
<a-doption
v-for="item in columnList"
:key="item.id"
:value="item.keyword"
>{{ item.name }}</a-doption
>
</template>
</a-dropdown>
<div class="grouping-list">
<div class="grouping-list-item" v-for="item in leftGroupingList">
<span class="name">{{ item.name }}</span>
<div class="del" @click="clickLeftDel(index)">
<icon-delete />
</div>
</div>
</div>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="2" title="查看数据">
<a-table
:columns="dataColumnList"
:data="columnListData"
:pagination="false"
size="small"
/>
</a-tab-pane>
</a-tabs>
<div v-else class="dissatisfy">
<div class="dissatisfy_box">
<img src="@/assets/imgs/oneNode.f1997e1e.png" alt="" />
<p>请将1个节点连接至本节点</p>
</div>
</div>
</template>
<script setup>
import http from "@/scripts/request";
import { ref, reactive, nextTick, toRefs } from "vue";
import { IconPlus, IconDelete } from "@arco-design/web-vue/es/icon";
const columnList = ref([]);
const leftGroupingList = ref([]); //left
let nodeValueData = ref({
type: false,
groupColumnNameList: [],
collectColumnList: [],
});
const props = defineProps({
//
nodeValue: {
type: Object,
default: () => {},
},
lineList: {
type: Array,
default: () => [],
},
conArr: {
type: Array,
default: () => [],
},
});
//使
const { nodeValue, lineList, conArr } = toRefs(props);
const lineListData = ref([]);
const conArrData = ref([]);
const disposeLineList = () => {
console.log("nodeValue-------------", nodeValue.value);
console.log("lineList-------------", lineList.value);
lineListData.value = [];
lineList.value.map((item) => {
if (item.toStr == nodeValue.value.id) {
lineListData.value.push(item);
}
});
console.log(lineListData.value);
lineListData.value.map((lineListDataItem) => {
conArr.value.map((conArrItem) => {
if (lineListDataItem.fromStr == conArrItem.id) {
conArrData.value.push(conArrItem);
columnList.value = JSON.parse(JSON.stringify(conArrItem.columnList));
columnList.value.map((item) => {
item.nodeId = nodeValue.value.id;
});
nodeValue.value.columnList = JSON.parse(
JSON.stringify(columnList.value)
);
console.log(nodeValue.value.data);
if (nodeValue.value.data) {
nodeValueData.value = JSON.parse(nodeValue.value.data);
leftGroupingList.value = [];
nodeValueData.value.groupColumnNameList.map((item) => {
columnList.value.map((groupColumnNameListItem) => {
if (groupColumnNameListItem.keyword == item) {
leftGroupingList.value.push(groupColumnNameListItem);
}
});
});
} else {
console.log("不存在");
nodeValueData.value = {
groupColumnNameList: [],
collectColumnList: [],
isRetain: false,
};
}
}
});
});
console.log("lineListData", lineListData.value);
console.log("conArrData", conArrData.value);
};
disposeLineList();
const tabsChange = (key) => {
console.log(key);
switch (key) {
case "2":
if (nodeValue.value.columnList) {
getDataStream();
}
break;
default:
break;
}
};
// left
const handleSelect = (keyword) => {
columnList.value.map((item) => {
if (item.keyword == keyword) {
leftGroupingList.value.push(item);
}
});
initData();
};
const clickLeftDel = (index) => {
leftGroupingList.value.splice(index, 1);
initData();
};
//
const initData = () => {
nodeValueData.value.groupColumnNameList = [];
leftGroupingList.value.map((item) => {
nodeValueData.value.groupColumnNameList.push(item.keyword);
});
nodeValue.value.data = JSON.stringify(nodeValueData.value);
console.log(nodeValue.value);
};
//
const dataColumnList = ref([])
const columnListData = ref([])
const getDataStream = () => {
http
.post("/api/metadata/data_stream/exec", {
lineList: lineList.value,
nodeList: conArr.value,
streamId: 1,
rootNodeId: nodeValue.value.id,
})
.then((resp) => {
dataColumnList.value = [];
resp.data.columnList.map((item) => {
dataColumnList.value.push({
title: item.name,
dataIndex: item.keyword,
});
});
columnListData.value = resp.data.dataList;
});
};
</script>
<style lang="scss" scoped>
.grouping-content {
display: flex;
.grouping-content-left {
flex: 0 0 260px;
padding: 0 16px;
box-sizing: border-box;
}
.group-title {
font-size: 14px;
display: flex;
align-items: center;
margin-bottom: 10px;
.label {
margin-right: 15px;
}
}
.grouping-content-right {
flex: auto;
padding: 0 16px;
box-sizing: border-box;
}
.grouping-list {
.grouping-list-item {
font-size: 14px;
margin-top: 10px;
display: flex;
align-items: center;
.name {
flex: 0 0 200px;
}
.del {
flex: 0 0 20px;
cursor: pointer;
margin-left: 20px;
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,205 @@
<template>
<div class="usedatabase">
<div class="usedatabase_left">
<a-tree :data="treeData" :load-more="loadMore" @select="select" />
</div>
<div class="usedatabase_right">
<div v-if="fieldList.length != 0">
<div>
<a-checkbox
:model-value="checkedAll"
:indeterminate="indeterminate"
@change="handleChangeAll"
>全选
</a-checkbox>
</div>
<a-checkbox-group
v-model="data"
@change="handleChange"
direction="vertical"
>
<a-checkbox :value="item.id" v-for="item in fieldList">{{
item.name
}}</a-checkbox>
</a-checkbox-group>
</div>
<div v-else class="empty">
<a-empty />
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import http from "@/scripts/request";
import { del } from "vue-demi";
const fieldList = ref([]);
const treeData = ref([]);
const metadataId = ref(""); //id
const metadataName = ref(""); //id
//
const getProject = () => {
http.get("/api/metadata/project").then((resp) => {
let arr = [];
resp.data.records.map((item) => {
arr.push({
title: item.name,
key: item.id,
children: [],
isLeaf: false,
});
});
treeData.value = arr;
});
};
//
const getMetadata = (id) => {
http
.get("/api/metadata/metadataColumn", {
metadataId: id,
})
.then((resp) => {
fieldList.value = resp.data.records;
//
checkedAll.value = true;
data.value = [];
resp.data.records.map((item) => {
data.value.push(item.id);
});
});
};
onMounted(() => {
getProject();
});
const select = (arr, nodeData) => {
if (nodeData.node.isLeaf) {
console.log(nodeData.node);
metadataName.value = nodeData.node.title;
metadataId.value = nodeData.node.key;
getMetadata(nodeData.node.key);
}
};
const loadMore = (nodeData) => {
console.log(nodeData);
return new Promise((resolve) => {
http
.get("api/metadata/metadata", {
projectId: nodeData.key,
})
.then((resp) => {
let arr = [];
resp.data.records.map((item) => {
arr.push({
title: item.name,
key: item.id,
isLeaf: true,
});
});
nodeData.children = arr;
resolve();
});
});
};
const indeterminate = ref(false);
const checkedAll = ref(false);
const data = ref([]);
const handleChangeAll = (value) => {
indeterminate.value = false;
if (value) {
checkedAll.value = true;
let valueData = [];
fieldList.value.map((item) => {
valueData.push(item.id);
});
data.value = valueData;
} else {
checkedAll.value = false;
data.value = [];
}
};
const handleChange = (values) => {
if (values.length === fieldList.value.length) {
checkedAll.value = true;
indeterminate.value = false;
} else if (values.length === 0) {
checkedAll.value = false;
indeterminate.value = false;
} else {
checkedAll.value = false;
indeterminate.value = true;
}
};
const getColumnList = (clickEfNodeId) => {
let filterData = [];
fieldList.value.map((item) => {
if (data.value.includes(item.id)) {
delete item.createdAt;
delete item.updatedAt;
filterData.push({
keyword: `${item.ename}_${clickEfNodeId}`,
nodeId: clickEfNodeId,
name: item.name,
ename: item.ename,
type: item.type,
sortNo: item.sortNo,
type: item.type,
typeStr: item.typeStr,
typeColor:item.typeColor
});
}
});
return {
name: metadataName.value,
metadataId: metadataId.value,
filterData: filterData,
};
};
const assignInit = (nodeValue) => {
let metadataId = JSON.parse(nodeValue.data).metadataId;
console.log(metadataId);
getMetadata(metadataId);
};
defineExpose({
getColumnList,
assignInit,
});
</script>
<style lang="scss">
.usedatabase {
width: 800px;
height: 400px;
display: flex;
.usedatabase_left {
flex: 0 0 200px;
padding-right: 20px;
margin-right: 20px;
border-right: 1px solid rgba(229, 230, 235, 1);
}
.usedatabase_right {
flex: 1;
.empty {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@ -0,0 +1,8 @@
<template>
<v-form-designer></v-form-designer>
</template>
<script setup>
</script>

View File

@ -0,0 +1,82 @@
<template>
<el-dialog :title="title"
v-model="visible"
:width="width"
destroy-on-close
@closed="$emit('closed')">
<el-upload
class="upload-demo"
:action="api"
:on-error="handlerError"
:before-upload="handlerBefore"
:on-success="handlerSuccess"
:show-file-list="false"
:headers="uploadHeader"
>
<el-button size="small" type="primary" >选择文件上传</el-button>
<template #tip>
<div class="el-upload__tip">仅允许导入"xls"或者"xlsx"格式文件, 点击<a href="javascript:;" @click="download" style="color: red;">这里</a>下载模板</div>
</template>
</el-upload>
</el-dialog>
</template>
<script>
import http from "@/utils/request";
import { ElLoading } from 'element-plus'
import tools from "@/utils/tools";
export default {
data () {
return {
title: '',
width: 400,
visible: false,
grape: '',
api: '',
loading: false,
loadingInstance: {},
uploadHeader:{
'x-token':'1212'
}
}
},
methods: {
handlerBefore(){
this.loadingInstance = ElLoading.service({
text: '导入中...'
})
},
handlerSuccess(resp) {
this.loadingInstance.close();
if(resp.code == 200) {
this.$alert(resp.message, '导入结果', {
confirmButtonText: '确定'
});
}
},
handlerError() {
this.loadingInstance.close();
},
download() {
http.download(`/api/data/excel/${this.grape}/template`);
},
open (title, width = 400, grape) {
let user = tools.data.get("user");
this.uploadHeader["x-token"] = `Bearer ${user.token}`
console.log(user)
this.title = title;
this.width = width;
this.visible = true;
this.grape = grape;
this.api = `/api/data/excel/${this.grape}/import`;
}
}
}
</script>

View File

@ -0,0 +1,89 @@
<template>
<el-tree :data="data.treeData"
:selectable="false"
show-checkbox
ref="treeRef"
node-key="key"
:check-strictly="checkStrictly"
:default-checked-keys="data.checkedKeys"
empty-text="暂无数据"
v-loading="data.loading"
/>
</template>
<script setup>
import { ref, reactive} from 'vue';
import { ElMessage } from 'element-plus'
import http from "@/utils/request.js";
const emits = defineEmits(['hideShow'])
const data = reactive({
treeData:[],
loading: true,
checkedKeys:[],
params:{}
})
const checkStrictly = ref(false)
const initTreeData = (url, params)=>{
console.log(params)
data.params = params;
http.get(url,params).then(resp => {
if(resp.code == 200){
data.loading = false;
data.treeData = resp.data
console.log(resp.checked)
let checked = JSON.parse(JSON.stringify(resp.checked))
diguiTree(resp.data,checked)
console.log(checked)
data.checkedKeys = checked
}
})
}
const treeRef = ref();
const postSubmit = (dialog)=>{
let checkedNodes = []
treeRef.value.getCheckedNodes().map(item=>{
checkedNodes.push(item.key)
})
//
treeRef.value.getHalfCheckedKeys().map(item=>{
checkedNodes.push(item)
})
dialog.loading = true;
let getCheckedKeys = checkedNodes.join(',')
data.params.ids = getCheckedKeys;
http.post(dialog.action.postUrl, data.params).then(resp=>{
if(resp.code == 200){
ElMessage.success("提交成功")
dialog.visibled = false;
dialog.loading = false;
}
})
}
const diguiTree = (data,checked)=>{
for (let i = 0; i < data.length ; i++) {
const item = data[i]
if (item.children && item.children.length > 0) {
if(checked.indexOf(item.value)!= -1){
checked.splice(checked.indexOf(item.value),1)
}
diguiTree(item.children,checked)
}
}
}
defineExpose({
initTreeData,
postSubmit
})
</script>

View File

@ -0,0 +1,240 @@
<template>
<div class="form-box" v-loading="loading">
<el-form
:model="formData"
:rules="data.ruleData"
:labelWidth="grape.labelWidth"
label-position="right"
ref="formRef"
>
<el-row :gutter="10" v-if="data.formList">
<template v-for="formColumn in data.formList" :key="formColumn.name">
<el-col
:span="formColumn.span"
v-if="formColumn.showOn ? evalExpr(formColumn.showOn) : true"
>
<el-form-item :label="formColumn.label" :prop="formColumn.name">
<Input :data="formColumn" :model="formData" />
</el-form-item>
</el-col>
</template>
</el-row>
<el-input type="hidden" v-model="formData[grape.primaryKey]" />
</el-form>
<el-row
type="flex"
justify="end"
v-if="!dialog.isDialog"
style="margin-top: 10px"
>
<el-button
type="primary"
@click="router.go(-1)"
v-if="dialog.mode == 'edit'"
plain
>
返回</el-button
>
<el-button type="primary" @click="submitFormData()" v-loading="saving">
提交</el-button
>
</el-row>
</div>
</template>
<script setup>
import { ref, toRefs, onMounted, reactive } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import emitter from "@/plugins/Bus.js";
import Input from "@/components/Input";
import tabTable from "./tabTable.vue";
import http from "@/utils/request";
const props = defineProps({
grape: {
type: Object,
default: {},
},
dialog: {
type: Object,
default: {},
},
});
const router = useRouter();
const { grape, dialog } = toRefs(props);
const data = reactive({
formList: null,
ruleData: {},
tabTable: null,
saving: false,
});
const formData = ref({ id: "" });
const loading = ref(true);
let url = `/api/build/${grape.value.name}/${dialog.value.mode}`;
const getGrapeFormData = () => {
http.get(url, { id: dialog.value.id }).then((resp) => {
if (resp.code == 200) {
formData.value = resp.data.data;
data.formList = resp.data.formList;
data.ruleData = resp.data.ruleList;
data.tabTable = resp.data.tabTable;
loading.value = false;
}
});
};
const getData = () => {
return formData.value;
};
const formRef = ref();
const tabTableRef = ref();
const tableData = ref([]);
const submitFormData = (tableParams) => {
formRef.value.validate(async (valid) => {
if (valid) {
if (grape.value.name == "RkOrder") {
tableData.value = tabTableRef.value.getTabTableData();
tableData.value.map(item=>{
item.num = item.price * item.amount
item.wzSpecId_mapping.map(item1=>{
if(item.wzSpecId == item1.value){
item.wzSpecTitle = item1.label
}
})
})
console.log(tableData.value);
console.log(data.tabTable,'数据');
} else {
data.saving = true;
let url = `/api/modify/${grape.value.name}`;
let formParams = {
data: formData.value,
};
if (dialog.value.mode == "add") {
let params = {};
for (let key in grape.value.params) {
params[grape.value.params[key]] = tableParams[key];
}
formParams.data = { ...formParams.data, ...params };
}
if (data.tabTable) {
formParams["tabData"] = tabTableRef.value.getTabTableData();
}
http[dialog.value.mode == "add" ? "post" : "put"](url, formParams).then(
(resp) => {
if (resp.code == 200) {
ElMessage.success("操作成功");
if (!dialog.value.isDialog && grape.value.formSubmitUrl) {
router.push({
path: grape.value.formSubmitUrl,
query: { id: resp.data.id },
});
} else {
emitter.emit(grape.value.name, {});
}
} else {
ElMessage.error(resp.message);
}
}
);
}
}
});
};
const secondary = () => {
let url = `/api/modify/${grape.value.name}`;
let formParams = {
data: formData.value,
};
if (dialog.value.mode == "add") {
let params = {};
for (let key in grape.value.params) {
params[grape.value.params[key]] = tableParams[key];
}
formParams.data = { ...formParams.data, ...params };
}
if (data.tabTable) {
formParams["tabData"] = tabTableRef.value.getTabTableData();
}
http[dialog.value.mode == "add" ? "post" : "put"](url, formParams).then(
(resp) => {
if (resp.code == 200) {
ElMessage.success("操作成功");
if (!dialog.value.isDialog && grape.value.formSubmitUrl) {
router.push({
path: grape.value.formSubmitUrl,
query: { id: resp.data.id },
});
} else {
emitter.emit(grape.value.name, {});
}
} else {
ElMessage.error(resp.message);
}
}
);
};
const evalExpr = (expr) => {
return eval(expr);
// return true;
};
const getSummaries = (param) => {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
const values = data.map((item) => Number(item[column.property]));
if (index === 2 || index === 3 || index === 4) {
sums[index] = ` ${values.reduce((prev, curr) => {
const value = Number(curr);
if (!Number.isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0)}`;
}else {
sums[index] = "";
}
});
sums[4] = sums[4] + "元";
sums[2] = sums[2] + '元'
return sums;
};
onMounted(() => {
getGrapeFormData();
});
defineExpose({
getData,
submitFormData,
});
</script>
<style>
.dialog{
width: 100%;
}
</style>

View File

@ -0,0 +1,200 @@
<template>
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header">
<span>{{ tabTable.label }}明细</span>
<el-button
class="button"
text
v-if="tabTable.selectGrape"
@click="lookup"
>选择{{ tabTable.label }}</el-button
>
</div>
</template>
<el-table
:data="data.rows"
border
:emptyText="data.emptyText"
:show-summary="data.showSummary"
:summary-method="getSummaryMethod"
>
<template v-for="column in tabTable.formList" :key="column.name">
<el-table-column
:label="column.label"
:prop="column.name"
:width="column.width"
>
<template #default="scope">
<Input :data="column" :model="data.rows[scope.$index]" />
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="80">
<template #default="scope">
<el-popconfirm
title="确定删除吗?"
confirm-button-text="确定"
cancel-button-text="取消"
@confirm="delTabTableData(scope.row, scope.$index)"
>
<template #reference>
<el-button type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
v-model="data.dialog.visibled"
destroy-on-close
v-if="tabTable.selectGrape"
:title="data.dialog.title"
:width="data.dialog.width"
>
<tableData
:grapeData="tabTable.selectGrape"
:params="{ parentGrape: tabTable.grape.name }"
:checkbox="true"
v-if="tabTable.selectGrape"
ref="tableDataRef"
></tableData>
<template #footer>
<el-button @click="handleCloseDialog"> </el-button>
<el-button type="primary" @click="handleSelectedData" :loading="loading"
> </el-button
>
</template>
</el-dialog>
</template>
<script setup>
import { ref, toRefs, onMounted, reactive } from "vue";
import Input from "@/components/Input";
import tableData from "./tableData.vue";
import http from "@/utils/request";
const props = defineProps({
tabTable: {
type: Object,
default: {},
},
});
const { tabTable } = toRefs(props);
const data = reactive({
rows: tabTable.value.data || [],
emptyText: `请选择${tabTable.value.label}`,
showSummary: tabTable.value.showSummary,
dialog: {
visibled: false,
title: "选择物资",
width: "800px",
loading: false,
},
});
const lookup = () => {
data.dialog.visibled = true;
};
const handleCloseDialog = () => {
data.dialog.visibled = false;
};
const tableDataRef = ref();
const handleSelectedData = () => {
data.dialog.loading = true;
var selectedData = tableDataRef.value.getSelectedData();
if (tabTable.value.selectGrape.url) {
var ids = selectedData.map((item) => {
return item[tabTable.value.selectGrape.grape.primaryKey];
});
http
.get(tabTable.value.selectGrape.url, { ids: ids.join(",") })
.then((resp) => {
if (resp.code == 200 && resp.data) {
data.rows = [...data.rows, ...resp.data];
}
data.dialog.loading = false;
data.dialog.visibled = false;
});
} else {
data.rows.concat(selectedData);
data.dialog.loading = false;
data.dialog.visibled = false;
}
};
const delTabTableData = (row, index) => {
data.rows.splice(index, 1);
};
const getTabTableData = () => {
return data.rows;
};
const getSummaryMethod = (param) => {
const sums = [];
// tabTable.sums = [6,7];
if (tabTable.value.sums) {
const { columns, data } = param;
columns.forEach((column, index) => {
if (index == 0) {
sums[0] = tabTable.value.summaryText;
} else if (tabTable.value.sums.indexOf(index) > -1) {
const values = data.map((item) => Number(item[column.property]));
if (values.length > 0) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
});
} else {
sums[index] = "";
}
} else if (index == 8) {
let n = 0
data.map(item=>{
let s = []
tabTable.value.sums.map(item2=>{
s.push(item[columns[item2].property])
})
let m = 1
s.map(item=>{
m = m*item
})
n += m
})
sums[8] = n+ "元";
} else {
sums[index] = "";
}
});
sums[6] = sums[6] + "元";
}
return sums;
};
defineExpose({
getTabTableData,
});
</script>
<style>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -0,0 +1,545 @@
<template>
<div class="table-box" v-loading="data.initLoading">
<el-row>
<el-col :span="grape.treeLink.span" v-if="grapeData.grape.treeLink">
<tree-link :grape="grape"></tree-link>
</el-col>
<el-col :span="grape.treeLink ? 24 - grapeData.grape.treeLink.span : 24">
<el-form class="searchContent" @keyup.enter.native="refreshTableData">
<el-row :gutter="10">
<el-col :span="6" v-for="formColumn in grapeData.filterList" :key="formColumn.name">
<Input :data="initFormColumn(formColumn)" :model="data.search" />
</el-col>
<el-col :span="6" class="flex-box col_box" :offset="offset(grapeData.filterList.length,4)">
<el-button @click="refreshTableData" v-if="grapeData.filterList.length >0" type="primary" plain
icon="Search">搜索</el-button>
<el-button @click="addFormData" v-if="power.add" type="primary" icon="Plus">新建</el-button>
<el-button type="primary" @click="clickDialogImport" v-if="power.imp">导入</el-button>
<el-button type="primary" @click="handleExportData" v-if="power.exp">导出</el-button>
<el-button @click.stop="handleAction(toolbar, scope)" type="primary"
v-for="toolbar in grapeData.toolbarList" :key="toolbar.name" v-if="grapeData.toolbarList.length> 0">
{{toolbar.label}}</el-button>
</el-col>
</el-row>
</el-form>
<el-table :data="data.rows" v-loading="data.tableLoading" stripe header-cell-class-name="table-header-gray"
ref="tableRef" empty-text="暂无数据" @selection-change="handleSelectionChange" row-key="id"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column :reserve-selection="true" type="selection" v-if="checkbox" />
<template v-for="column in grapeData.columnList" :key="column.name">
<el-table-column v-if="column.type == 'bool' || column.type == 'image' || column.type == 'images'"
:label="column.label" :prop="column.name" :sortable="column.sortable ? 'custom' : false"
:width="column.width">
<template #default="scopeBool" v-if="column.type == 'bool'">
<el-tag size="mini" :type="scopeBool.row[column.name] ? 'success' : 'danger'">
{{scopeBool.row[column.name] ? column.dict[true] : column.dict[false]}}
</el-tag>
</template>
<template #default="scopeImage" v-else-if="column.type == 'image'">
<el-image :src="scopeImage.row[column.name].preview" :fit="fit" style="width: 60px; height: 60px"
v-if="scopeImage.row[column.name] && scopeImage.row[column.name].preview"></el-image>
</template>
<template #default="scopeImages" v-else-if="column.type == 'images'">
<el-image v-for="(item,key) in scopeImages.row[column.name].slice(0,2)" :key="key" :src="item.preview"
style="width: 60px; height: 60px;margin-right:10px"></el-image>
<div class="images_more" v-if="scopeImages.row[column.name].length > 2"><i class="el-icon-more"></i>
</div>
</template>
</el-table-column>
<el-table-column v-else :label="column.label" :prop="column.name" :width="column.width">
<template #default="scope">
<div style="display: inline-block;">
<div style="display: flex; align-items: center">
{{scope.row[column.name]}}
<el-tooltip placement="top" effect="light" v-if="scope.row[`${column.name}__tips`]">
<template #content>
<div v-html="scope.row[`${column.name}__tips`]"></div>
</template>
<el-icon style="margin-left: 4px;">
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</div>
</template>
</el-table-column>
</template>
<el-table-column label="操作" :width="grape.actionWidth" v-if="actionShow()">
<template #default="scope">
<span v-if="power.edit" style="margin-right:12px ;">
<el-button type="text" size="small" @click="editFormData(scope.row)"
v-if="filterShowOn(power.editShowOn,scope.row)">编辑</el-button>
</span>
<template v-for="action in grapeData.actionList" :key="action.label">
<span v-if="action.confirm">
<el-popconfirm v-if="filterHandleAction(action, scope.row)" :title="action.confirm"
confirm-button-text="确定" cancel-button-text="取消" @confirm="handleAction(action, scope)">
<template #reference>
<el-button style="margin:0 12px 0 0 ;" type="text" size="small">{{action.label}}</el-button>
</template>
</el-popconfirm>
</span>
<el-button style="margin:0 12px 0 0 ;" v-else-if="filterHandleAction(action, scope.row)" type="text"
size="small" @click.stop="handleAction(action, scope)">{{action.label}}</el-button>
</template>
<el-popconfirm title="确定删除吗?" confirm-button-text="确定" cancel-button-text="取消"
@confirm="delGrapeData(scope.row, scope.$index)"
v-if="power.delete && filterShowOn(power.deleteShowOn,scope.row)">
<template #reference>
<el-button style="margin:0 " type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="pagination-box" v-if="grape.isPage">
<el-pagination small layout="prev, pager, next" @current-change="currentChange"
:current-page="data.search.page" :page-size="data.search.pageSize" :total="data.tableTotal" />
</div>
<el-dialog v-model="data.dialog.visibled" :title="data.dialog.title" :width="data.dialog.width" destroy-on-close
v-if="power.add || power.edit">
<formData :grape="grape" :dialog="data.dialog" ref="formDataRef"></formData>
<template #footer>
<el-button @click="handleCloseDialog"> </el-button>
<el-button type="primary" @click="handleSubmitFormData"> </el-button>
</template>
</el-dialog>
<!-- 动态组件 -->
<el-dialog v-model="data.actionDialog.visibled" :title="data.actionDialog.title"
:width="data.actionDialog.width" destroy-on-close>
<component :is="data.actionDialog.type" ref="actionDialogRef"></component>
<template #footer>
<el-button @click="handleCloseActionDialog"> </el-button>
<el-button type="primary" @click="handleSubmitActionDialog" v-loading="data.actionDialog.loading">确认
</el-button>
</template>
</el-dialog>
<!-- end 动态组件 -->
<div class="dialogTable" v-if="dialogTableData.show">
<img class="close" src="@/assets/images/close.png" alt="" @click="dialogTableData.show = false">
<tableData :grapeData="dialogTableData.grapeData" :params="dialogTableData.params"></tableData>
</div>
</el-col>
</el-row>
</div>
<GrapeDialogImport v-if="dialogImport" ref="grapeDialogImport" :grapeWidget="grapeWidget" @closed="dialog=false"/>
</template>
<script setup>
import Input from '@/components/Input';
import { ref, toRefs, onMounted, reactive, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import emitter from '@/plugins/Bus';
import formData from './formData.vue';
import treeLink from './treeLink.vue';
import dialogTree from './dialogTree.vue';
import tableData from './tableData.vue'
import http from '@/utils/request';
import { useRouter } from 'vue-router'
import GrapeDialogImport from './dialogImport.vue';
import tools from "@/utils/tools";
const router = useRouter()
const props = defineProps({
grapeData: {
type: Object,
default: {}
},
checkbox: {
type: Boolean,
default: false
},
params: {
type: Object,
default: {}
}
});
const { grapeData, parentGrape, checkbox, params } = toRefs(props);
const grape = grapeData.value.grape;
const power = grapeData.value.power;
const formDataRef = ref();
console.log(grape.name)
const data = reactive({
rows: [],
grape: grapeData.value.grape,
initLoading: true,
tableLoading: false,
dialog: {
title: `新建 ${grape.title}`,
visibled: false,
mode: 'add',
width: grape.formWidth,
type: 'dialog-tree',
isDialog: true,
id: ''
},
actionDialog: {
title: '',
visibled: false,
width: 600,
action: {},
loading: false
},
tableTotal: 0,
search: {
page: 1,
pageSize: 20,
},
selectedData: [],
})
const getGrapeTableData = () => {
if (!data.initLoading) data.tableLoading = true;
http.get(`/api/data/table/${grape.name}`, { ...params.value, ...data.search }).then(resp => {
data.tableLoading = false
if (resp.code == 200) {
data.rows = resp.data;
data.tableTotal = resp.count
}
data.initLoading = false;
})
}
//
const currentChange = (page) => {
data.search.page = page
getGrapeTableData()
}
const addFormData = () => {
data.dialog = {
title: `新建 ${grape.title}`,
mode: 'add',
width: grape.formWidth,
id: '',
visibled: true,
isDialog: true
}
}
const refreshTableData = () => {
getGrapeTableData();
}
const editFormData = (row) => {
data.dialog = {
title: `编辑 ${grape.title}`,
mode: 'edit',
width: grape.formWidth,
id: row[grape.primaryKey],
visibled: true,
isDialog: true
}
}
const delGrapeData = (row, index) => {
http.delete(`/api/modify/${grape.name}/${row[grape.primaryKey]}`).then(resp => {
if (resp.code == 200) {
data.rows.splice(index, 1);
ElMessage.success("删除成功")
} else {
ElMessage.error(resp.message);
}
})
}
const handleCloseDialog = () => {
data.dialog.visibled = false;
}
const handleSubmitFormData = () => {
formDataRef.value.submitFormData(params.value);
}
const offset = (i, n) => {
let m = i < n ? i : i % n
return (n - m - 1) * 6
}
const tableRef = ref();
const handleSelectionChange = (val) => {
data.selectedData = val;
}
const getSelectedData = () => {
return data.selectedData;
}
const initFormColumn = (formColumn) => {
switch (formColumn.type) {
case 'radio-group':
formColumn.type = 'select'
break;
case 'bool':
formColumn.type = 'select'
formColumn.options = []
for (const key in formColumn.dict) {
if (Object.hasOwnProperty.call(formColumn.dict, key)) {
const element = formColumn.dict[key];
formColumn.options.push({
label: element,
value: key,
key: key
})
}
}
break;
default:
break;
}
return formColumn
}
//actionList
const dialogTableData = reactive({
show: false,
grapeData: {},
params: {}
})
const openDialogTable = (grapeName, params) => {
http.get(`/api/build/${grapeName}`).then(res => {
dialogTableData.params = params
dialogTableData.grapeData = res.data
dialogTableData.show = true
})
}
const actionDialogRef = ref();
const handleAction = (action, scope) => {
let params = {};
for (let key in action.params) {
params[key] = scope.row[action.params[key]];
}
switch (action.type) {
case 'ajax':
if (action.getUrl != '') {
http.get(action.getUrl, params).then(res => {
if (res.code == 200) {
if (res.message && res.message != '') {
ElMessage.success(res.message)
}
if (action.reload) {
getGrapeTableData()
}
} else {
ElMessage.error(res.message);
}
})
} else if (action.postUrl != '') {
http.post(action.postUrl, params).then(res => {
if (res.code == 200) {
if (res.message && res.message != '') {
ElMessage.success(res.message)
}
if (action.reload) {
getGrapeTableData()
}
} else {
ElMessage.error(res.message);
}
})
}
break;
case 'dialog-tree':
data.actionDialog = {
title: action.label,
width: action.width,
type: dialogTree,
visibled: true,
action: action
}
nextTick(() => {
actionDialogRef.value.initTreeData(action.getUrl, params)
})
break;
case 'dialog-table':
openDialogTable(action.grapeName, params)
break;
case 'jump':
router.push({
path: action.getUrl,
query: params
})
break;
case 'open':
var url = action.getUrl;
http.download(url);
break;
default:
console.log('其他', action.type)
}
}
const handleSubmitActionDialog = () => {
actionDialogRef.value.postSubmit(data.actionDialog)
}
const handleCloseActionDialog = () => {
data.actionDialog = {
visibled: false
}
}
emitter.on(grape.name, e => {
data.search = { ...data.search, ...e };
data.dialog.visibled = false
getGrapeTableData()
})
onMounted(() => {
getGrapeTableData();
});
defineExpose({
getSelectedData
});
const filterHandleAction = (action, row) => {
if (action.power) {
var actionPower = power[action.power];
if (actionPower != undefined && !actionPower) return false;
}
if (action.self) {
if (row.self != undefined && !row.self) return false;
}
if (action.showOn) {
return filterShowOn(action.showOn, row);
}
return true;
}
const handleExportData = () => {
var url = `/api/data/excel/${grape.name}/export`;
url = tools.url(url, data.search);
http.download(url);
}
//
const actionShow = () => {
console.log('power', power)
let actionShowIf = false
if (power.delete || power.edit) {
return true
}
if (grapeData.value.actionList.length > 0) {
grapeData.value.actionList.map(item => {
if (item.power) {
if (power[item.power] == undefined || power[item.power]) {
actionShowIf = true
}
} else {
actionShowIf = true
}
})
}
return actionShowIf;
}
const filterShowOn = (expr, row) => {
if (!row.id) {
return false
}
if (!expr) return true;
return eval(expr);
// return true;
};
//
const dialogImport = ref(false)
const grapeDialogImport = ref(null)
const clickDialogImport = () => {
dialogImport.value = true;
console.log(data.grape.name)
nextTick(() => {
grapeDialogImport.value.open("导入数据", 400, grape.name);
});
}
</script>
<style lang="scss">
.flex-box {
display: flex !important;
justify-content: flex-end;
}
.col_box {
margin-bottom: 10px;
}
.ydool_input {
margin-bottom: 5px;
}
.dialogTable {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: #ffffff;
z-index: 99;
box-sizing: content-box;
.close {
position: absolute;
right: -25px;
top: -20px;
width: 30px;
cursor: pointer;
}
}
.el-dialog {
margin: 0 !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.el-dialog__body {
max-height: 60vh;
overflow: auto;
}
.table-box .el-button--small {
padding: 0;
}
</style>
<style lang="scss">
.content {
overflow-y: auto;
overflow-x: hidden;
;
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<el-card class="box-card" shadow="never" style="margin-right: 10px;">
<template #header>
<div class="card-header">
<span>{{grape.treeLink.nav}}</span>
</div>
</template>
<el-tree
:data="treeData"
@nodeClick="handleChange"
empty-text="暂无数据"
/>
</el-card>
</template>
<script setup>
import {onMounted, toRefs, ref} from 'vue'
import emitter from '@/plugins/Bus.js'
import http from '@/utils/request';
const props = defineProps({
grape:{
type: Object,
default: {}
},
})
const {grape}= toRefs(props)
const treeData = ref([]);
const getTreeData = ()=>{
http.get(`/api/data/tree/${grape.value.treeLink.grapeClass}`).then(resp => {
if(resp.code == 200){
treeData.value = resp.data;
}
})
}
const handleChange = (newValue)=> {
var name = grape.value.treeLink.pid;
var params = {};
params[name] = newValue.value;
emitter.emit(grape.value.name, params);
}
onMounted(()=>{
getTreeData()
})
</script>
<style>
</style>

View File

@ -0,0 +1,318 @@
<template>
<div id="treeTableData">
<div class="treeTableData_left">
<el-button
class="search_but"
type="primary"
icon="Plus"
@click="clickAdd"
style="width: 100%;
margin-bottom: 10px;"
v-if="power.add"
>新增</el-button
>
<el-input v-model="filterText" placeholder="搜索" />
<el-scrollbar style="height: calc(100% - 72px); width: 100%">
<el-tree
v-loading="data.initLoading"
ref="treeRef"
class="filter-tree"
:data="TreeData"
:props="defaultProps"
:filter-node-method="filterNode"
:expand-on-click-node="false"
@node-click="nodeClick"
/>
</el-scrollbar>
</div>
<div class="treeTableData_right">
<div
class="form_content scroll-container"
v-loading="formGrape.isDialog"
v-if="dialog.isDialog"
>
<el-form
:model="formData"
:rules="formGrape.ruleData"
:labelWidth="grape.labelWidth"
label-position="right"
ref="formRef"
>
<el-row :gutter="10" v-if="formGrape.formList">
<template
v-for="formColumn in formGrape.formList"
:key="formColumn.name"
>
<el-col
:span="formColumn.span"
v-if="formColumn.showOn ? evalExpr(formColumn.showOn) : true"
>
<el-form-item :label="formColumn.label" :prop="formColumn.name">
<Input :data="formColumn" :model="formData" />
</el-form-item>
</el-col>
</template>
</el-row>
<el-input type="hidden" v-model="formData[grape.primaryKey]" />
</el-form>
<el-row
type="flex"
justify="center"
style="margin-top: 10px; margin-bottom: 40px"
>
<el-button
type="primary"
@click="submitFormData()"
v-loading="saving"
v-if="power.add || power.edit"
>
提交</el-button
>
<el-button
@click="clickDel()"
v-loading="saving"
v-if="power.edit && dialog.mode == 'edit'"
>
删除</el-button
>
</el-row>
</div>
<div class="" v-else>
<el-empty description="请选择对象" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, reactive, toRefs, onMounted } from "vue";
import http from "@/utils/request";
import emitter from "@/plugins/Bus.js";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({
grapeData: {
type: Object,
default: {},
},
});
const data = reactive({
initLoading: true,
});
const TreeData = ref([]);
const { grapeData, grapeName } = toRefs(props);
const grape = grapeData.value.grape;
const power = grapeData.value.power;
console.log("grapeData", grapeData.value);
const getGrapeTableData = () => {
if (!data.initLoading) data.tableLoading = true;
http.get(`/api/data/table/${grape.name}`).then((resp) => {
data.tableLoading = false;
if (resp.code == 200) {
console.log(resp.data);
TreeData.value = resp.data;
}
data.initLoading = false;
});
};
const filterText = ref("");
const treeRef = ref();
const defaultProps = {
children: "children",
label: "title",
};
watch(filterText, (val) => {
treeRef.value.filter(val);
});
const filterNode = (value, data) => {
if (!value) return true;
return data.title.includes(value);
};
const dialog = reactive({
id: "",
mode: "add",
visibled: true,
isDialog: false,
});
const formData = ref({});
const formGrape = reactive({
formList: [],
ruleData: [],
tabTable: [],
isDialog: true,
});
const getGrapeFormData = () => {
formGrape.isDialog = true;
let url = `/api/build/${grape.name}/${dialog.mode}`;
http.get(url, { id: dialog.id }).then((resp) => {
if (resp.code == 200) {
formData.value = resp.data.data;
formGrape.formList = resp.data.formList;
formGrape.ruleData = resp.data.ruleList;
formGrape.tabTable = resp.data.tabTable;
formGrape.isDialog = false;
dialog.isDialog = true;
}
});
};
const evalExpr = (expr) => {
return eval(expr);
// return true;
};
//tree
const nodeClick = (node) => {
console.log(node.id);
dialog.node = node;
dialog.id = node.id;
dialog.mode = "edit";
dialog.isDialog = true;
formGrape.isDialog = true;
getGrapeFormData();
};
const clickAdd = () => {
dialog.node = {};
dialog.id = "";
dialog.mode = "add";
dialog.isDialog = true;
getGrapeFormData();
};
const formRef = ref();
const submitFormData = (tableParams) => {
formRef.value.validate(async (valid) => {
if (valid) {
data.saving = true;
let url = `/api/modify/${grape.name}`;
let formParams = {
data: formData.value,
};
if (dialog.mode == "add") {
let params = {};
for (let key in grape.params) {
params[grape.params[key]] = tableParams[key];
}
formParams.data = { ...formParams.data, ...params };
}
if (data.tabTable) {
formParams["tabData"] = tabTableRef.value.getTabTableData();
}
http[dialog.mode == "add" ? "post" : "put"](url, formParams).then(
(resp) => {
if (resp.code == 200) {
ElMessage.success("操作成功");
dialog.isDialog = false;
dialog.id = ''
dialog.mode = 'add'
getGrapeTableData();
} else {
ElMessage.error(resp.message);
}
}
);
}
});
};
const clickDel = () => {
ElMessageBox.confirm("确定是否删除?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
http
.delete(`/api/modify/${grape.name}/${dialog.node[grape.primaryKey]}`)
.then((resp) => {
if (resp.code == 200) {
ElMessage.success("删除成功");
getGrapeTableData();
dialog.isDialog = false;
} else {
ElMessage.error(resp.message);
}
});
})
.catch(() => {
console.log("取消");
});
};
onMounted(() => {
getGrapeFormData();
getGrapeTableData();
});
</script>
<style lang="scss">
#treeTableData {
display: flex;
height: calc(100vh - 100px);
.treeTableData_left {
flex: 0 0 250px;
padding: 20px;
border: 1px solid #efefef;
border-radius: 4px;
height: 100%;
box-sizing: border-box;
margin-right: 20px;
}
.treeTableData_right {
flex: auto;
padding: 20px;
border: 1px solid #efefef;
border-radius: 4px;
}
}
.search_content {
display: flex;
margin-bottom: 20px;
.search_content_item {
width: 200px;
}
.search_but {
margin-left: 10px;
}
}
.treeTableData_right {
.form_content {
height: calc(100% - 35px);
}
}
.scroll-container {
overflow-y: auto;
overflow-x: hidden;
}
.scroll-container::-webkit-scrollbar-track {
background-color: #fff;
}
.scroll-container::-webkit-scrollbar {
width: 5px;
height: 2px;
background-color: #fff;
}
.scroll-container::-webkit-scrollbar-thumb {
background-color: #cfcfcf;
border-radius: 2px;
}
</style>

48
src/views/grape/index.vue Normal file
View File

@ -0,0 +1,48 @@
<template>
<div class="content">
<treeTableData
:grapeData="grapeData"
v-if="type == 'treeForm' && grapeData"
></treeTableData>
<tableData :grapeData="grapeData"
v-if="type == 'crud' && grapeData"></tableData>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRoute } from "vue-router";
import tableData from "./components/tableData.vue";
import formData from "./components/formData.vue";
import treeTableData from "./components/treeTableData.vue";
import http from "@/utils/request";
const route = useRoute();
const grapeName = route.meta.grape;
const type = ref();
const grapeData = ref();
const dialog = {
id: route.query.id,
mode: route.query.id ? "edit" : "add",
title: route.query.id ? "修改" : "新建",
visibled: true,
isDialog: false,
};
http.get(`/api/build/${grapeName}`).then((resp) => {
if (resp.code == 200) {
grapeData.value = resp.data;
type.value = resp.data.grape.pageType
}
});
</script>
<style scoped>
.content {
background-color: #fff;
padding: 0;
min-height: calc(100vh - 100px);
}
</style>

5
src/views/home/index.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<div>
首页
</div>
</template>

View File

@ -0,0 +1,43 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<el-breadcrumb-item :to="{name: 'welcome'}" :replace="true">
<el-icon :size="14">
<HomeFilled />
</el-icon>
</el-breadcrumb-item>
<transition-group name="breadcrumb" mode="out-in">
<template v-for="item in breadList" :key="item.title" >
<el-breadcrumb-item v-if="item.path!='/' && !item.meta.hiddenBreadcrumb" :key="item.meta.title">{{item.meta.title}}</el-breadcrumb-item>
</template>
</transition-group>
</el-breadcrumb>
</template>
<script setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter();
const breadList = ref(route.meta.breadcrumb);
router.beforeEach((to, form, next) => {
console.log(to);
breadList.value = to.meta.breadcrumb;
next();
})
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<el-menu-item
ref="item"
:index="menu.path"
:key="menu.name"
v-if="
menu.children == null ||
menu.children.length <= 0 ||
metaHidden(menu.children)
"
>
<template #title>
<el-icon v-if="menu.meta.icon" style="margin-right: 5px">
<component :is="menu.meta.icon" style="width: 18px; height: 18px" />
</el-icon>
<span>{{ menu.meta.title }}</span>
</template>
</el-menu-item>
<el-sub-menu v-else :index="menu.name">
<template #title>
<el-icon v-if="menu.meta.icon" style="margin-right: 5px">
<component :is="menu.meta.icon" style="width: 18px; height: 18px" />
</el-icon>
<span>{{ menu.meta.title }}</span>
</template>
<template v-for="child in menu.children" :key="child.name">
<menu-item
:class="['nest-menu', route.path == child.name ? 'on' : '']"
:menu="child"
:isChild="true"
v-if="!child.meta.hidden"
/>
</template>
</el-sub-menu>
</template>
<script setup>
import { useRoute } from "vue-router";
import { toRefs } from "vue";
const props = defineProps({
menu: Object,
});
const route = useRoute();
const { menu } = toRefs(props);
const metaHidden = (menu) => {
let hiddenIf = true;
menu.map((item) => {
if (!item.meta.hidden) {
hiddenIf = false;
}
});
return hiddenIf;
};
</script>
<style>
.grape-app .on .el-menu-item {
background-color: hsla(0deg, 0%, 100%, 0.3) !important;
}
</style>

346
src/views/layout/index.vue Normal file
View File

@ -0,0 +1,346 @@
<template>
<el-container v-if="store.menuBar == 'left'">
<el-aside :class="['aside', isCollapse ? 'isCollapse' : '']">
<div class="NavMenu_title">
<el-icon :size="28">
<eleme />
</el-icon>
<span class="title" v-if="!isCollapse">{{ store.title }}</span>
</div>
<el-scrollbar
wrap-class="scrollbar_dropdown__wrap"
style="height: calc(100vh - 60px)"
>
<el-menu
:uniqueOpened="true"
class="el-menu-vertical-demo"
background-color="#086dd9"
text-color="#fff"
active-text-color="#ffffff"
:collapse="isCollapse"
:router="true"
:default-active="route.path"
>
<template v-for="menu in user.menus" :key="menu.name">
<menu-item :menu="menu" />
</template>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<el-header class="yd_header">
<div style="display: flex; align-items: center">
<el-icon :size="24" class="icon" @click="isCollapse = !isCollapse">
<expand v-if="isCollapse" />
<fold v-else />
</el-icon>
<breadcrumb style="margin-left: 20px;" />
</div>
<el-space wrap :size="25">
<el-icon :size="24" class="icon" @click="screen">
<full-screen />
</el-icon>
<el-badge is-dot class="item" style="font-size: 0">
<el-icon :size="24" class="icon">
<bell-filled />
</el-icon>
</el-badge>
<el-dropdown class="userCenter">
<div class="el-dropdown-link">
<el-avatar
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
:size="24"
></el-avatar>
<span class="name">admin</span>
<el-icon>
<arrow-down />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Avatar">个人中心</el-dropdown-item>
<el-dropdown-item icon="SwitchButton">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-space>
</el-header>
<el-main>
<router-view v-slot="{ Component }">
<component :is="Component" :key="route.fullPath" />
</router-view>
</el-main>
</el-container>
</el-container>
<el-container v-else-if="store.menuBar == 'top'">
<el-header class="yd_header_top">
<div class="yd_header_top_left">
<div class="yd_header_top_title">
<el-icon :size="28">
<eleme />
</el-icon>
<span class="title" v-if="!isCollapse">{{ store.title }}</span>
</div>
<el-menu
class="el-menu-demo"
mode="horizontal"
@select="handleSelect"
text-color="#fff"
active-text-color="#ffffff"
:router="true"
:default-active="route.path"
>
<template v-for="menu in user.menus" :key="menu.name">
<menu-item :menu="menu" />
</template>
</el-menu>
</div>
<el-space wrap :size="25">
<el-icon :size="24" class="icon" @click="screen">
<full-screen />
</el-icon>
<el-badge is-dot class="item" style="font-size: 0">
<el-icon :size="24" class="icon">
<bell-filled />
</el-icon>
</el-badge>
<el-dropdown class="userCenter">
<div class="el-dropdown-link">
<el-avatar
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
:size="24"
></el-avatar>
<span class="name">admin</span>
<el-icon>
<arrow-down />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Avatar">个人中心</el-dropdown-item>
<el-dropdown-item icon="Avatar" @click="handleUserMenu"
>退出</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-space></el-header
>
<div class="breadcrumb">
<breadcrumb />
</div>
<el-container>
<el-main>
<router-view v-slot="{ Component }">
<component :is="Component" :key="route.fullPath" />
</router-view>
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import { ref, reactive } from "vue";
import { useCar } from "@/store";
import { useRoute, useRouter } from "vue-router";
import { ElMessageBox } from "element-plus";
import emitter from "@/plugins/Bus";
import tools from "@/utils/tools";
import menuItem from "./components/menu/menu-item.vue";
import breadcrumb from "./components/breadcrumb/index.vue";
const user = tools.data.get("user");
const route = useRoute();
let store = useCar();
const isCollapse = ref(false);
//
const flag = ref(false);
const screen = () => {
let element = document.documentElement;
// null,false,
flag.value = document.fullscreenElement === null ? false : true;
// false
if (flag.value) {
// 退
if (document.exitFullscreen) {
document.exitFullscreen();
}
} else {
//
if (element.requestFullscreen) {
element.requestFullscreen();
}
}
//
flag.value = !flag.value;
};
const data = reactive({
dialog: {
visibled: false,
},
});
emitter.on("closeModifyPassword", (e) => {
data.dialog.visibled = false;
});
const handleUserMenu = (command) => {
if ("modifyPassword" == command) {
data.dialog.visibled = true;
} else if ("logout" == command) {
ElMessageBox.confirm("确定要退出当前系统吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
tools.data.clear();
Router.push("/login");
});
}
};
</script>
<style lang="scss">
.aside {
width: 220px;
background-color: rgb(8, 109, 217);
transition: all 0.4s;
&.isCollapse {
width: 64px;
}
}
.NavMenu_title {
height: 60px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #ffffff;
overflow: hidden;
.title {
font-size: 18px;
margin-left: 10px;
white-space: nowrap;
}
}
.el-container {
height: 100vh;
.el-header {
background-color: #ffffff;
}
}
.yd_header_top {
.el-popper.is-pure {
background-color: #1890ff !important;
}
}
.el-menu {
background-color: transparent;
border-right: none;
}
.el-menu-item {
height: 48px;
line-height: 48px;
}
.el-menu-item.is-active {
background-color: #1890ff !important;
}
.yd_header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
.icon {
color: #1890ff;
cursor: pointer;
}
}
.el-dropdown-link {
display: flex;
align-items: center;
}
.el-dropdown-link {
outline: none;
}
.userCenter {
outline: none;
cursor: pointer;
}
.yd_header {
.el-space__item:last-child {
margin-right: 0 !important;
}
}
.el-dropdown-link {
.name {
margin: 0 10px;
font-size: 14px;
}
}
.yd_header_top {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #1890ff !important;
overflow: hidden;
color: #ffffff;
.yd_header_top_left {
display: flex;
height: 100%;
.yd_header_top_title {
display: flex;
align-items: center;
color: #ffffff;
overflow: hidden;
width: 220px;
.title {
font-size: 18px;
margin-left: 10px;
white-space: nowrap;
}
}
.el-menu--horizontal .el-menu-item:not(.is-disabled):hover {
background-color: #096dd9;
}
.el-menu--horizontal > .el-sub-menu .el-sub-menu__title:hover {
background-color: #096dd9;
}
}
.el-dropdown-link {
color: #ffffff;
}
}
.el-menu--horizontal .el-menu .el-menu-item.is-active,
.el-menu--horizontal .el-menu .el-sub-menu.is-active > .el-sub-menu__title {
color: #1890ff !important;
background-color: #e6f7ff !important;
}
.el-menu--horizontal .el-menu .el-menu-item,
.el-menu--horizontal .el-menu .el-sub-menu__title {
color: #595959 !important;
}
.is-light .el-menu--horizontal .el-menu-item:not(.is-disabled):focus,
.is-light .el-menu--horizontal .el-menu-item:not(.is-disabled):hover {
color: #1890ff !important;
}
.breadcrumb {
padding: 20px 10px;
background-color: #f0f0f0;
}
</style>

252
src/views/login/index.vue Normal file
View File

@ -0,0 +1,252 @@
<template>
<div class="main">
<div class="login">
<div class="bd5_left"></div>
<div class="bd5_right"></div>
<div class="login_image"></div>
<div class="auto">
<span class="auto_title"> 登录 </span>
<el-form
:model="data"
size="medium"
ref="formRef"
@keyup.enter.native="handleSubmit"
>
<el-row :span="22">
<el-form-item prop="user">
<el-input
v-model="data.user"
style="width: 100%; margin-bottom: 20px"
placeholder="帐号"
prefix-icon="User"
></el-input>
</el-form-item>
</el-row>
<el-row :span="22">
<el-form-item prop="password">
<el-input
v-model="data.password"
style="width: 100%; margin-bottom: 48px"
type="password"
placeholder="密码"
prefix-icon="Lock"
show-password
>
</el-input>
</el-form-item>
</el-row>
<el-button
class="login-btn-submit"
type="primary"
@click="handleSubmit()"
:loading="loading"
>
<span class="info1">&nbsp;</span>
</el-button>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import tools from "@/utils/tools";
import http from "@/utils/request.js";
import { encrypt } from "@/utils";
const Router = useRouter();
const data = reactive({
user: "",
password: "",
});
const loading = ref(false);
const formRef = ref();
const handleSubmit = async () => {
formRef.value.validate(async (valid) => {
if (valid) {
loading.value = true;
var params = {
user: encrypt(data.user),
password: encrypt(data.password),
};
http.post("/api/auth/login", params).then((resp) => {
if (resp.code == 200) {
var user = {
token: resp.data.token,
name: resp.data.name,
menus: resp.data.menus,
buttons: resp.data.buttons,
accountType: resp.data.accountType,
};
tools.data.set("user", user);
ElMessage.success("登录成功");
//loading.value = false;
Router.push("/");
//
} else {
ElMessage.error(resp.message);
loading.value = false;
}
});
}
});
};
</script>
<style lang="scss">
.main {
.login {
.auto {
.el-row{
width: 100%;
.el-form-item{
width: 100%;
}
}
.el-input__inner {
width: 100%;
height: 40px;
border: none;
}
.el-input__wrapper {
border: 1px solid #a8abb2;
background-color: #fff;
box-shadow: none;
}
.el-form-item__content {
-webkit-user-select: none; /*谷歌 /Chrome*/
-moz-user-select: none; /*火狐/Firefox*/
-ms-user-select: none; /*IE 10+*/
user-select: none;
width: 100%;
}
}
}
}
</style>
<style lang="scss" scoped>
.main {
height: 100vh;
width: 100%;
.login {
position: relative;
background: url(@/assets/images/login_bg_3.png);
background-size: 80% 100%;
width: 100%;
height: 100%;
background-repeat: no-repeat;
.login_image {
z-index: 31;
position: absolute;
left: 0;
top: 150px;
width: 45%;
height: 599px;
background: url(@/assets/images/login_bg_1.png) -1px 0px no-repeat;
background-size: contain;
}
.bd5_left {
z-index: 313;
position: absolute;
left: -38px;
bottom: 0;
width: 297px;
height: 113px;
background: url(@/assets/images/login_bg_4.png) -1px 0px no-repeat;
}
.bd5_right {
z-index: 267;
position: absolute;
right: 20%;
top: 0;
width: 297px;
height: 113px;
background: url(@/assets/images/login_bg_2.png) 0px 0px no-repeat;
}
}
.auto {
z-index: 4;
height: 350px;
width: 436px;
position: absolute;
padding: 58px 32px;
left: 50%;
transform: translateY(-50%);
top: 50%;
background: #fefefe;
box-shadow: 10px 10px 0px 0px #e4e8ec;
border-radius: 2px;
backdrop-filter: blur(50px);
.auto_title {
display: block;
width: 60px;
height: 33px;
font-size: 30px;
margin-bottom: 48px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #0081eb;
line-height: 33px;
}
.login-btn-submit {
width: 100%;
height: 48px;
background: #0081eb;
border-radius: 4px;
padding: 0;
}
.info1 {
width: 52px;
height: 24px;
font-size: 22px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #ffffff;
line-height: 24px;
}
}
}
@media screen and (max-width: 996px) {
.main .login .login_image {
width: 80%;
left: 50%;
top: 50%;
transform: translate(-50%, -60%);
}
.main .auto {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 99;
}
.main .login {
background-size: 100% 100%;
}
}
@media screen and (max-width: 662px) {
.main .login .login_image {
width: 100%;
transform: translate(-56%, -60%);
}
}
@media screen and (max-width: 420px) {
.main .auto {
width: 100%;
box-sizing: content-box;
}
}
</style>

36
vite.config.js Normal file
View File

@ -0,0 +1,36 @@
import { defineConfig } from "vite";
import { fileURLToPath, URL } from 'url'
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
// import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
// resolvers: [ElementPlusResolver()],
}),
Components({
// resolvers: [ElementPlusResolver()],
}),
],
resolve: {
extensions: ['.js', '.vue'],
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0', // 解决“vite use `--host` to expose”-- 0.0.0.0 将监听所有地址
proxy: {
'/api': {
target: 'https://fywz.btdit.cn/',
changeOrigin: true,
// ws: true,// 开启webSocket
}
}
}
});

1121
yarn.lock Normal file

File diff suppressed because it is too large Load Diff