chore(frontend): 落地 ADS 治理体系并收录前端底座基线代码
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<el-breadcrumb class="h-[50px] flex items-center">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
||||
<span
|
||||
v-if="
|
||||
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
|
||||
"
|
||||
class="text-[var(--el-disabled-text-color)]"
|
||||
>{{ item.meta.title }}</span
|
||||
>
|
||||
<a v-else @click.prevent="handleLink(item)">
|
||||
{{ item.meta.title }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref, watch } from "vue";
|
||||
import { useRoute, RouteLocationMatched } from "vue-router";
|
||||
import { compile } from "path-to-regexp";
|
||||
import router from "@/framework/router";
|
||||
// import { translateRouteTitleI18n } from "@/framework/utils/i18n";
|
||||
|
||||
const currentRoute = useRoute();
|
||||
const pathCompile = (path: string) => {
|
||||
const { params } = currentRoute;
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
|
||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>);
|
||||
|
||||
function getBreadcrumb() {
|
||||
console.log(currentRoute.matched,8888);
|
||||
|
||||
let matched = currentRoute.matched.filter(
|
||||
(item) => item.meta && item.meta.title
|
||||
);
|
||||
// const first = matched[0];
|
||||
// if (!isDashboard(first)) {
|
||||
// matched = [
|
||||
// { path: "/home", meta: { title: "首页" } } as any,
|
||||
// ].concat(matched);
|
||||
// }
|
||||
breadcrumbs.value = matched.filter((item) => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
||||
});
|
||||
}
|
||||
|
||||
function isDashboard(route: RouteLocationMatched) {
|
||||
const name = route && route.name;
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
name.toString().trim().toLocaleLowerCase() ===
|
||||
"首页"
|
||||
);
|
||||
}
|
||||
|
||||
function handleLink(item: any) {
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
router.push(pathCompile(path)).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => currentRoute.path,
|
||||
(path) => {
|
||||
if (path.startsWith("/redirect/")) {
|
||||
return;
|
||||
}
|
||||
getBreadcrumb();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getBreadcrumb();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
// 覆盖 element-plus 的样式
|
||||
.el-breadcrumb__inner,
|
||||
.el-breadcrumb__inner a {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="not-container">
|
||||
<img src="@/assets/img/403.png" class="not-img" alt="403" />
|
||||
<div class="not-detail">
|
||||
<h2>403</h2>
|
||||
<h4>抱歉,您无权访问该页面~🙅♂️🙅♀️</h4>
|
||||
<el-button type="primary" @click="router.push('/')"> 返回首页 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="403">
|
||||
// import { HOME_URL } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="not-container">
|
||||
<img src="@/assets/img/404.png" class="not-img" alt="404" />
|
||||
<div class="not-detail">
|
||||
<h2>404</h2>
|
||||
<h4>抱歉,您访问的页面不存在~</h4>
|
||||
<h4>有可能是您没有访问权限~</h4>
|
||||
<el-button type="primary" @click="router.push('/')"> 返回首页 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
// import { HOME_URL } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="not-container">
|
||||
<img src="@/assets/img/500.png" class="not-img" alt="500" />
|
||||
<div class="not-detail">
|
||||
<h2>500</h2>
|
||||
<h4>抱歉,您的网络不见了~🤦♂️🤦♀️</h4>
|
||||
<el-button type="primary" @click="router.push('/')"> 返回首页 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="500">
|
||||
// import { HOME_URL } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
.not-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.not-img {
|
||||
margin-right: 120px;
|
||||
}
|
||||
.not-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
h2,
|
||||
h4 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 60px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
h4 {
|
||||
margin: 30px 0 20px;
|
||||
font-size: 19px;
|
||||
font-weight: normal;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
.el-button {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<!-- <div class="hambuger-wrapper" :class="{active: isActive}" @click="toggleClick">
|
||||
<el-icon size="20">
|
||||
<Expand />
|
||||
</el-icon>
|
||||
</div> -->
|
||||
<div class="hambuger-wrapper" @click="toggleClick">
|
||||
<img :src="foldPng" style="width: 28px;height: 28px;" :style="{transform: isActive ? '' :'rotate(180deg)'}"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import foldPng from '@/assets/img/icon/fold.png'
|
||||
defineProps({
|
||||
isActive: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["toggleClick"]);
|
||||
|
||||
function toggleClick() {
|
||||
emit("toggleClick");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hambuger-wrapper{
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
background: rgba(0,0,0,.025);
|
||||
}
|
||||
}
|
||||
.active{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<img class="menu-img" :src="imgSrc"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { getAssetsImages } from '@/framework/utils/index'
|
||||
const props = defineProps({
|
||||
path: {
|
||||
type: String,
|
||||
default: "/menu",
|
||||
},
|
||||
imgName: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
}
|
||||
});
|
||||
|
||||
const imgSrc = computed(() => getAssetsImages(props.imgName, props.path));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-img{
|
||||
margin-left: 3px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="批量添加" :destroy-on-close="true" width="580px" draggable top="1vh">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="模板下载:">
|
||||
<el-button type="primary" @click="downloadFile">
|
||||
<el-icon color="#fff">
|
||||
<Download />
|
||||
</el-icon> 模板下载
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件上传:">
|
||||
<el-upload class="upload" drag :action="uploadUrl" :on-success="excelUploadSuccess" :on-error="excelUploadError" :before-upload="beforeExcelUpload">
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
请按模板上传文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import type {UploadRawFile } from 'element-plus'
|
||||
|
||||
let props = defineProps({
|
||||
templateName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
uploadUrl: String
|
||||
})
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 下载模板
|
||||
const downloadFile = () => {
|
||||
if (props.templateName === '') {
|
||||
return ElMessage({ type: 'error', message: '未配置模板下载路径' })
|
||||
}
|
||||
|
||||
let dom = document.createElement("a");
|
||||
dom.style.display = 'none'
|
||||
dom.target = '_blank'
|
||||
dom.href = `./templete/${props.templateName}`
|
||||
document.body.appendChild(dom);
|
||||
dom.click();
|
||||
document.body.removeChild(dom);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 文件上传之前判断
|
||||
* @param file 上传的文件
|
||||
* */
|
||||
const beforeExcelUpload = (file: UploadRawFile) => {
|
||||
const suffix = file.name.split('.').pop()
|
||||
const isExcel = ['xls', 'xlsx'].includes(suffix);
|
||||
if (!isExcel)
|
||||
ElMessage({type: 'warning',message: '上传文件只能是 xls / xlsx 格式' })
|
||||
|
||||
// const fileSize = file.size
|
||||
// if (!fileSize)
|
||||
// setTimeout(() => {
|
||||
// ElNotification({
|
||||
// title: "温馨提示",
|
||||
// message: `上传文件大小不能超过 ${parameter.value.fileSize}MB!`,
|
||||
// type: "warning"
|
||||
// });
|
||||
// }, 0);
|
||||
return isExcel;
|
||||
};
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const excelUploadSuccess = (response: any) => {
|
||||
emit('success', response)
|
||||
}
|
||||
|
||||
// 上传错误提示
|
||||
const excelUploadError = () => {
|
||||
ElMessage({
|
||||
title: "温馨提示",
|
||||
message: `批量添加失败,请您重新上传!`,
|
||||
type: "error"
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
dialogVisible
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.upload {
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="" :class="'pagination ' + { hidden: hidden }">
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:background="background"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, PropType } from "vue";
|
||||
import { scrollTo } from "@/framework/utils/scroll-to";
|
||||
|
||||
const props = defineProps({
|
||||
total: {
|
||||
required: true,
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array as PropType<number[]>,
|
||||
default() {
|
||||
return [10, 20, 30, 50];
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: "total, sizes, prev, pager, next, jumper",
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["pagination", "update:page", "update:limit"]);
|
||||
|
||||
const currentPage = useVModel(props, "page", emit);
|
||||
|
||||
const pageSize = useVModel(props, "limit", emit);
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
emit("pagination", { page: currentPage, limit: val });
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
emit("pagination", { page: val, limit: props.limit });
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding: 20px 12px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<el-select v-model="value" :placeholder="placeholder" style="width: 100%;" clearable :filterable="filterable">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" style="max-width:480px;"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onMounted} from 'vue'
|
||||
/**
|
||||
* SearchSelect
|
||||
* @author nielang
|
||||
* @description 用于实时检索的下拉框
|
||||
* @property {String} placeholder input的placeholder
|
||||
* @property {Function} request 接口请求函数
|
||||
* @property {String} requestParam 接口请求参数名称
|
||||
* @property {Boolean} showOptionValue 选项中是否显示value属性
|
||||
* @property {String} optionValue 接口用于显示的属性(该属性将返回父组件用于检索)
|
||||
* @property {String} optionLabel 接口用于显示的属性
|
||||
* @property {Boolean} filterable 是否可搜索
|
||||
*/
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
request:{
|
||||
type: Function,
|
||||
require: true
|
||||
},
|
||||
requestParam: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
|
||||
showOptionValue: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
optionValue: {
|
||||
type: [String, Number],
|
||||
default: "id",
|
||||
},
|
||||
optionLabel: {
|
||||
type: [String, Number],
|
||||
default: "name",
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
interface ListItem {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
const options = ref<ListItem[]>([])
|
||||
const initOptions = () => {
|
||||
if (props.request) {
|
||||
props.request(props.requestParam).then((data:any) => {
|
||||
options.value = data.map((item: any) => {
|
||||
return {
|
||||
value: item[props.optionValue],
|
||||
label: (props.showOptionValue ? `【${item[props.optionValue]}】` : '') + item[props.optionLabel]
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<el-select v-model="value" remote :placeholder="placeholder" style="width: 100%;" filterable
|
||||
:remote-method="remoteMethod" :loading="loading" clearable >
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" style="max-width:480px"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed} from 'vue'
|
||||
/**
|
||||
* SearchSelect
|
||||
* @author nielang
|
||||
* @description 用于实时检索的下拉框
|
||||
* @property {String} placeholder input的placeholder
|
||||
* @property {Function} request 接口请求函数
|
||||
* @property {String} requestParam 接口请求参数名称
|
||||
* @property {Boolean} showOptionValue 选项中是否显示value属性
|
||||
* @property {String} optionValue 接口用于显示的属性(该属性将返回父组件用于检索)
|
||||
* @property {String} optionLabel 接口用于显示的属性
|
||||
*/
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入商品编码/名称",
|
||||
},
|
||||
request:{
|
||||
type: Function,
|
||||
require: true
|
||||
},
|
||||
requestParam: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
showOptionValue: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
optionValue: {
|
||||
type: [String, Number],
|
||||
default: "id",
|
||||
},
|
||||
optionLabel: {
|
||||
type: [String, Number],
|
||||
default: "name",
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false);
|
||||
interface ListItem {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
|
||||
const options = ref<ListItem[]>([])
|
||||
const remoteMethod = (query: string) => {
|
||||
if (query) {
|
||||
loading.value = true
|
||||
if (props.request) {
|
||||
const params:any = {}
|
||||
params[props.requestParam] = query
|
||||
props.request(params).then((data:any) => {
|
||||
loading.value = false
|
||||
options.value = data.map((item: any) => {
|
||||
return {
|
||||
value: item[props.optionValue],
|
||||
label: (props.showOptionValue ? `【${item[props.optionValue]}】` : '') + item[props.optionLabel]
|
||||
}
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
options.value = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
<!--主题-->
|
||||
<template>
|
||||
<div class="form-container ab-m-t-16 title">
|
||||
<el-page-header :icon="null" >
|
||||
<template #title>
|
||||
<div style="display:flex;align-items: center;">
|
||||
<slot name="titleBack"></slot>
|
||||
<span style="font-size: 25px;font-weight: 500;"><slot name="titleName"></slot></span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<span style="font-size: 15px;"><slot name="titleContent"></slot></span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<slot name="titleOperate"></slot>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title{
|
||||
padding: 10px 0px 10px 10px;;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<!-- 内容分隔组件 -->
|
||||
<template>
|
||||
<div class="title-container" >
|
||||
<div class="header" :style="{ backgroundColor: bgColor, fontSize, color, borderBottom: arrow?'1px solid #CDD0D6':'' }" @click="handleShow" >
|
||||
<div class="left">
|
||||
<span class="line" :style="{display: showLine? 'inline' : 'none', borderColor: lineColor}"></span> <span class="name">{{ title }}</span>
|
||||
</div>
|
||||
<div class="right" :style="{display: arrow? 'inline' : 'none'}">
|
||||
<el-icon :style="{transform: show ? 'rotate(90deg)' : '', transition: 'transform 0.2s linear'}"><ArrowRightBold /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" v-show="show" :style="{height: show ? 'auto' : '0px'}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, required: true, }, //标签名称
|
||||
bgColor: { type: String, default: '#fff' },//背景颜色
|
||||
color: { type: String, default: '#222' },//文字颜色
|
||||
fontSize: { type: String, default: '20px' },//字体大小
|
||||
showLine: { type: Boolean, default: true },//是否显示图标
|
||||
lineColor: { type: String, default: '' },//图标颜色
|
||||
arrow: { type: Boolean, default: false },//展开、收起功能
|
||||
})
|
||||
|
||||
const show = ref(true)
|
||||
const handleShow = () => {
|
||||
if (!props.arrow) {
|
||||
return
|
||||
}
|
||||
show.value = !show.value
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
// border-bottom: 1px solid #CDD0D6;
|
||||
|
||||
.left{
|
||||
.line {
|
||||
width: 2px;
|
||||
border: 2px solid var(--el-color-primary);
|
||||
border-radius: 6px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.right{
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.content{
|
||||
height: 0px;
|
||||
transition: height 2s ease;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- 点击复制 -->
|
||||
<template >
|
||||
<el-tooltip v-if="content" content="复制" placement="top" effect="dark">
|
||||
<el-icon class="ab-cursor-pointer" @click="useCopy(content)"><CopyDocument /></el-icon>
|
||||
</el-tooltip>
|
||||
{{ content }}
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/framework/hooks/useCopy'
|
||||
|
||||
const props = defineProps({
|
||||
content: { type: String, required: true,}, //内容
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!--文档预览组件-->
|
||||
<template>
|
||||
<el-dialog title="预览" :model-value="dialogVisible" :before-close="handleClose" :modal="false" :show-close="true" :close-on-click-modal="false" append-to-body>
|
||||
<template #title>
|
||||
<span class="dialog-title">
|
||||
<el-button type="primary" @click="downloadFile()">下载文件</el-button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div v-if="['doc', 'docx'].includes(fileType)">
|
||||
<vue-office-docx :src="fileUrl" style="height: 70vh;overflow: auto" @rendered="rendered" @error="errorHandler" />
|
||||
</div>
|
||||
<div v-else-if="fileType == 'xlsx'">
|
||||
<vue-office-excel :src="fileUrl" style="height: 70vh;overflow: auto" @rendered="rendered" @error="errorHandler" />
|
||||
</div>
|
||||
<div v-else-if="fileType == 'pdf'">
|
||||
<vue-office-pdf :src="fileUrl" style="height: 70vh;overflow: auto" @rendered="rendered" @error="errorHandler" :options="{width: '100%',overflow: 'auto'}" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="['png', 'jpg', 'jpeg','gif'].includes(fileType)">
|
||||
<el-image :src="fileUrl" style="height: 70vh;overflow: auto" @rendered="rendered" @error="errorHandler" :options="{width: '100%',overflow: 'auto'}" />
|
||||
</div>
|
||||
|
||||
<div v-else="">
|
||||
<el-input v-model="textarea1" style="height: 70vh;overflow: auto" autosize type="textarea" :options="{width: '100%',overflow: 'auto'}" />
|
||||
</div>
|
||||
|
||||
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// 引入VueOffice组件
|
||||
import VueOfficeDocx from '@vue-office/docx'
|
||||
import VueOfficeExcel from '@vue-office/excel'
|
||||
import VueOfficePdf from '@vue-office/pdf'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
// 引入相关样式
|
||||
import '@vue-office/docx/lib/index.css'
|
||||
import '@vue-office/excel/lib/index.css'
|
||||
|
||||
const textarea1=ref('')
|
||||
const emit = defineEmits(["closePreviewDialog"]);
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
fileName: { type: String, default: '' },// 文件url
|
||||
fileType: { type: String, default: '' },// 文件url
|
||||
fileUrl: { type: String, default: '' },// 文件url
|
||||
dialogVisible: { type: Boolean, default: false },// 控制显示
|
||||
})
|
||||
|
||||
const downloadFile = () => {
|
||||
if(XEUtils.startsWith(props.fileUrl, './')||XEUtils.startsWith(props.fileUrl, '/')){
|
||||
window.location.href=props.fileUrl
|
||||
}else{
|
||||
window.location.href=props.fileUrl
|
||||
}
|
||||
}
|
||||
|
||||
const rendered = () => {
|
||||
console.log('渲染完成')
|
||||
}
|
||||
const errorHandler = (e:any) => {
|
||||
console.error('渲染失败',e)
|
||||
ElMessage({message: '文档渲染失败',type: 'error',})
|
||||
}
|
||||
const handleClose = (done) => {
|
||||
emit('closePreviewDialog', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!-- 动态表单组件 -->
|
||||
<!-- 新功能禁止使用该组件 -->
|
||||
<template>
|
||||
<div class="form-container">
|
||||
<el-form ref="formRef" label-width="82px" :model="props.formModel" :rules="dFormRules">
|
||||
<el-row :gutter="props.gutter">
|
||||
<template v-for="(value, key, index) in props.formModel">
|
||||
<el-col class="ab-p-b-5" v-if="'section'===fieldConfigMap.get(key)?.type" :span="24" v-show="isShow(fieldConfigMap.get(key))">
|
||||
<AbSection :title="fieldConfigMap.get(key).name"></AbSection>
|
||||
</el-col>
|
||||
<el-col v-else :span="fieldConfigMap.get(key)?.span ? fieldConfigMap.get(key).span : props.span" v-show="isShow(fieldConfigMap.get(key))">
|
||||
<template v-if="fieldConfigMap.get(key)?.slot">
|
||||
<el-form-item :label-width="fieldConfigMap.get(key)?.labelWidth" :label="fieldConfigMap.get(key)?.name" :prop="fieldConfigMap.get(key)?.key" :required="fieldConfigMap.get(key)?.required">
|
||||
<slot :name="fieldConfigMap.get(key)?.slot"></slot>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<AbFormItem :formModel="props.formModel" :fieldSet="fieldConfigMap.get(key)"></AbFormItem>
|
||||
</template>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #title>
|
||||
</template>
|
||||
<el-col :span="24" class="ab-text-right ab-m-b-18">
|
||||
<slot v-if="$slots['button']" name="button"></slot>
|
||||
<template v-else>
|
||||
<el-button type="primary" @click="executeSave(formRef)">保存</el-button>
|
||||
<el-button type="default" @click="formReset()">重置</el-button>
|
||||
</template>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import AbFormItem from "@/framework/components/standard/FormDynamic/abFormItem.vue";
|
||||
import AbSection from "@/framework/components/standard/AbSection/index.vue";
|
||||
const formRef = ref<FormInstance>() //表单引用
|
||||
const dFormRules = reactive<FormRules>({}) //验证规则
|
||||
const fieldConfigMap = ref(new Map()) //表单明细配置,Map<表单函数key,表单函数key对应配置>
|
||||
//组件传参
|
||||
let props = defineProps({
|
||||
formModel: { type: Object },//表单绑定数据
|
||||
fieldConfigs: { type: Array },//表单明细配置
|
||||
gutter: { type: Number, default: 0 },//栅格间隔
|
||||
span: { type: Number, default: 6 },//栅格占据的列数
|
||||
saveEvent: { type: Function },//保存事件
|
||||
resetEvent: { type: Function },//重置事件
|
||||
})
|
||||
|
||||
// 外部使用响应式函数,不需要监听
|
||||
// watch(() => props.fieldConfigs,(newValue, oldValue) => {
|
||||
// console.log(newValue);
|
||||
// },{ deep: true }
|
||||
// )
|
||||
|
||||
/**是否显示 */
|
||||
const isShow = (fieldConfig) => {
|
||||
if(fieldConfig&&undefined!==fieldConfig.show&&!fieldConfig.show)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
/**数据处理 */
|
||||
const dataHandle = (fieldConfigs) => {
|
||||
for (let fc of fieldConfigs) {
|
||||
//转换map,方便获取数据
|
||||
fieldConfigMap.value.set(fc.key, fc)
|
||||
//设置表单验证规则
|
||||
if (undefined !== fc.rules) {
|
||||
dFormRules[fc.key] = fc.rules
|
||||
}
|
||||
//绑定表单默认数据
|
||||
if(!props.formModel[fc.key])
|
||||
props.formModel[fc.key] = (undefined !== fc.value ? fc.value : '')
|
||||
}
|
||||
}
|
||||
|
||||
dataHandle(props.fieldConfigs)
|
||||
|
||||
/**执行查询 */
|
||||
const executeSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl || !await formEl.validate((valid, fields) => { return valid })) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.saveEvent) props.saveEvent()
|
||||
}
|
||||
|
||||
/**重置查询条件 */
|
||||
const formReset = () => {
|
||||
if (props.resetEvent) {
|
||||
props.resetEvent()
|
||||
return
|
||||
}
|
||||
for (let fc of props.fieldConfigs) {
|
||||
props.formModel[fc.key] = (undefined !== fc.value ? fc.value : '')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<!-- 动态表单明细组件 -->
|
||||
<!-- 新功能禁止使用该组件 -->
|
||||
<template>
|
||||
<el-form-item :label-width="props.fieldSet?.labelWidth" :label="props.fieldSet?.name" :prop="props.fieldSet?.key" :required="props.fieldSet?.required">
|
||||
<!-- 文本 -->
|
||||
<el-text v-if="props.fieldSet?.type==='text'" style="width: 100%;" v-model="formModel[props.fieldSet?.key]" >
|
||||
<Text :data="formModel[props.fieldSet?.key]"></Text>
|
||||
</el-text>
|
||||
<!-- 文本框 -->
|
||||
<el-input v-else-if="props.fieldSet?.type==='input'" v-model="formModel[props.fieldSet?.key]"
|
||||
:placeholder="props.fieldSet?.placeholder?props.fieldSet?.placeholder:'请输入'+props.fieldSet?.name"
|
||||
:disabled="props.fieldSet?.disabled" />
|
||||
<!-- 下拉框 -->
|
||||
<el-select v-else-if="props.fieldSet?.type==='select'" v-model="formModel[props.fieldSet?.key]"
|
||||
:placeholder="props.fieldSet?.placeholder?props.fieldSet?.placeholder:'请选择'+props.fieldSet?.name"
|
||||
:disabled="props.fieldSet?.disabled" clearable filterable style="width: 100%;">
|
||||
<el-option v-for="item in props.fieldSet?.listData" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" />
|
||||
</el-select>
|
||||
<!-- 单选 -->
|
||||
<el-radio-group v-else-if="props.fieldSet?.type==='radio'" v-model="formModel[props.fieldSet?.key]" :disabled="props.fieldSet?.disabled">
|
||||
<el-radio v-for="item in props.fieldSet?.listData" :label="item.value" :disabled="item.disabled">{{item.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- 多选 -->
|
||||
<el-checkbox-group v-else-if="props.fieldSet?.type==='checkbox'" v-model="formModel[props.fieldSet?.key]" :disabled="props.fieldSet?.disabled">
|
||||
<el-checkbox v-for="item in props.fieldSet?.listData" :label="item.value" :disabled="item.disabled">{{item.label}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<!-- 时间选择器 -->
|
||||
<el-date-picker style="width: 100%;" v-else-if="dataPickerType.find((o: any) => o.type === props.fieldSet?.type) " v-model="formModel[props.fieldSet?.key]" :disabled="props.fieldSet?.disabled"
|
||||
:type="props.fieldSet?.type" unlink-panels range-separator="-"
|
||||
:start-placeholder="props.fieldSet?.placeholder?'开始'+props.fieldSet?.placeholder:'开始'+props.fieldSet?.name"
|
||||
:end-placeholder="props.fieldSet?.placeholder?'结束'+props.fieldSet?.placeholder:'结束'+props.fieldSet?.name"
|
||||
:value-format="dataPickerType.find((o: any) => o.type === props.fieldSet?.type).valueFormat" />
|
||||
<!-- 不指定类型、无类型不输出 -->
|
||||
<template v-else></template>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Text from "@/framework/components/standard/Text/index.vue";
|
||||
/**时间选择器配置 */
|
||||
let dataPickerType=[
|
||||
{type:'year',valueFormat:'YYYY'},
|
||||
{type:'month',valueFormat:'YYYY-MM'},
|
||||
{type:'date' ,valueFormat:'YYYY-MM-DD'},
|
||||
{type:'dates' ,valueFormat:'YYYY-MM-DD'},
|
||||
{type:'datetime' ,valueFormat:'YYYY-MM-DD HH:mm:ss'},
|
||||
{type:'week' },
|
||||
{type:'datetimerange' ,valueFormat:'YYYY-MM-DD HH:mm:ss'},
|
||||
{type:'daterange' ,valueFormat:'YYYY-MM-DD'},
|
||||
{type:'monthrange',valueFormat:'YYYY-MM'},
|
||||
]
|
||||
let props = defineProps({
|
||||
formModel: {type:Object},//表单函数
|
||||
editable:{type:Boolean,default:true},//可编辑
|
||||
//当前字段设置
|
||||
fieldSet: {
|
||||
type:Object,
|
||||
default:{
|
||||
key:String,//字段Key
|
||||
name:String, //显示名称
|
||||
placeholder:String, //默认提示
|
||||
required:Boolean,//是否必填
|
||||
labelWidth:{type:String,default:'82px'},//标签宽度
|
||||
show: { type: Boolean, default: true },//是否显示
|
||||
disabled:Boolean,//是否禁用
|
||||
type:String,//输入类型,当前支持input、select、radio、checkbox
|
||||
//列表数据,当type为select、radio、checkbox时使用
|
||||
listData:{
|
||||
type:Array,
|
||||
default:[
|
||||
{
|
||||
value:String,//值
|
||||
text:String,//文本显示、
|
||||
disabled:Boolean,//是否禁用
|
||||
}
|
||||
]},
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<!-- 动态查询表单组件 -->
|
||||
<!-- 新功能禁止使用该组件 -->
|
||||
<template>
|
||||
<div class="form-container">
|
||||
<div class="more-filter" @click="criteriaShow = !criteriaShow" v-if="criteriaShowNumber < fieldConfigMap.size">
|
||||
{{ criteriaShow ? '收起' : '展开' }}更多筛选
|
||||
<el-icon v-if="criteriaShow"><ArrowDown /></el-icon>
|
||||
<el-icon v-else><ArrowUp /></el-icon>
|
||||
</div>
|
||||
|
||||
<el-form ref="formRef" label-width="82px" :model="props.formModel" :rules="dFormRules">
|
||||
<el-row :gutter="props.gutter">
|
||||
<template v-for="(value, key, index) in props.formModel">
|
||||
<el-col :span="fieldConfigMap.get(key).span ? fieldConfigMap.get(key).span : props.span" v-show="isShow(fieldConfigMap.get(key))&&(!(props.criteriaShowNumber-1<index)?true:criteriaShow)">
|
||||
<!-- 自定义插槽-->
|
||||
<template v-if="fieldConfigMap.get(key).slot">
|
||||
<el-form-item :label-width="fieldConfigMap.get(key)?.labelWidth" :label="fieldConfigMap.get(key)?.name" :prop="fieldConfigMap.get(key)?.key" :required="fieldConfigMap.get(key)?.required">
|
||||
<slot :name="fieldConfigMap.get(key).slot"></slot>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- 固定类型-->
|
||||
<template v-else>
|
||||
<AbFormItem :formModel="props.formModel" :fieldSet="fieldConfigMap.get(key)"></AbFormItem>
|
||||
</template>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :span="3" class="ab-text-right ab-m-b-18" :offset="criteriaShow ? props.queryCriteriaOffset[1] : props.queryCriteriaOffset[0]">
|
||||
<el-button type="primary" v-throttle:click.delay[1000]="()=>executeQuery(formRef)" >查询</el-button>
|
||||
<el-button type="default" @click="formReset()">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import AbFormItem from "@/framework/components/standard/FormDynamic/abFormItem.vue";
|
||||
const criteriaShow = ref(false) //查询条件隐藏功能
|
||||
const formRef = ref<FormInstance>() //表单引用
|
||||
const dFormRules = reactive<FormRules>({}) //验证规则
|
||||
const fieldConfigMap = ref(new Map()) //表单明细配置,Map<表单函数key,表单函数key对应配置>
|
||||
//组件传参
|
||||
let props = defineProps({
|
||||
formModel: { type: Object },//表单绑定数据
|
||||
fieldConfigs: { type: Array },//表单明细配置
|
||||
gutter: { type: Number, default: 0 },//栅格间隔
|
||||
span: { type: Number, default: 6 },//栅格占据的列数
|
||||
queryEvent: { type: Function },//查询事件
|
||||
resetEvent: { type: Function },//重置事件
|
||||
queryCriteriaOffset: { type: Array, default: [0,6] },//查询条件偏移量,[收起偏移量,展开偏移量]
|
||||
criteriaShowNumber: { type: Number, default: 6 },//查询条件默认显示数量
|
||||
})
|
||||
|
||||
// 外部使用响应式函数,不需要监听
|
||||
// watch(() => props.fieldConfigs,(newValue, oldValue) => {
|
||||
// console.log(newValue);
|
||||
// },{ deep: true }
|
||||
// )
|
||||
|
||||
/**是否显示 */
|
||||
const isShow = (fieldConfig) => {
|
||||
if(undefined!==fieldConfig.show&&!fieldConfig.show)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
/**数据处理 */
|
||||
const dataHandle = (fieldConfigs) => {
|
||||
for (let fc of fieldConfigs) {
|
||||
//转换map,方便获取数据
|
||||
fieldConfigMap.value.set(fc.key, fc)
|
||||
//设置表单验证规则
|
||||
if (undefined !== fc.rules) {
|
||||
dFormRules[fc.key] = fc.rules
|
||||
}
|
||||
//绑定表单默认数据
|
||||
if(!props.formModel[fc.key])
|
||||
props.formModel[fc.key] = (undefined !== fc.value ? fc.value : '')
|
||||
}
|
||||
}
|
||||
|
||||
dataHandle(props.fieldConfigs)
|
||||
|
||||
/**执行查询 */
|
||||
const executeQuery = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl || !await formEl.validate((valid, fields) => { return valid })) {
|
||||
return
|
||||
}
|
||||
if (props.queryEvent) props.queryEvent()
|
||||
}
|
||||
|
||||
/**重置查询条件 */
|
||||
const formReset = () => {
|
||||
if (props.resetEvent) {
|
||||
props.resetEvent()
|
||||
return
|
||||
}
|
||||
for (let fc of props.fieldConfigs) {
|
||||
props.formModel[fc.key] = (undefined !== fc.value ? fc.value : '')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<!-- Excel上传组件 -->
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="文件上传" :destroy-on-close="true" width="580px" draggable top="1vh">
|
||||
<el-row justify="end">
|
||||
<el-col :span="24" style="text-align: center;">
|
||||
<el-text size="large">注意事项:上传文件必须符合模板格式要求,点此<el-link type="primary" @click="downloadFile"><el-text type="primary" size="large">下载模板</el-text></el-link >! </el-text>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-upload ref="fileUpload" :headers="ht" :data="props.data" drag :auto-upload="false" :show-file-list="true" :action="props.uploadUrl" :on-success="excelUploadSuccess" :on-error="excelUploadError" :before-upload="beforeExcelUpload">
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<el-row v-show="''!==uploadResult" style="margin-top:10px" justify="end">
|
||||
<el-col :span="24" style="text-align: right;">
|
||||
<el-alert type="success" :closable="false">
|
||||
<template #default>
|
||||
<el-text >
|
||||
上传结果:本次一共导入数据[<el-text type="primary">{{ uploadResult.totalNum }}</el-text>]条,
|
||||
成功[<el-text type="success" >{{ uploadResult.successNum }}</el-text>]条,
|
||||
<el-text v-if="uploadResult.updateNum>0" >修改[<el-text type="success" >{{ uploadResult.updateNum }}</el-text>]条,</el-text>
|
||||
失败[<el-text type="warning" >{{ uploadResult.failureNum }}</el-text>]条,
|
||||
<el-link type="primary" v-if="uploadResult.filePath!==''" :href="uploadResult.filePath" target="_blank">下载错误信息</el-link>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-top:10px" justify="end">
|
||||
<el-col :span="12" style="text-align: right;">
|
||||
<el-button type="primary" @click="submitFile">上传</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {UploadRawFile } from 'element-plus'
|
||||
import { getAllHeader } from "@/framework/store/modules/user";
|
||||
|
||||
|
||||
const fileUpload=ref()
|
||||
|
||||
const ht=getAllHeader()
|
||||
|
||||
let props = defineProps({
|
||||
//上传时附带的额外参数
|
||||
data: {},
|
||||
//模板名称(模板需要放在public/templete)
|
||||
templateName: { type: String, default: '', },
|
||||
//文件上传地址
|
||||
uploadUrl: String,
|
||||
//是否自定义结果处理
|
||||
customResultHandle:{ type: Boolean, default: false },
|
||||
//上传文件最大值,单位(MB)
|
||||
uploadMaxSize: { type: Number, default: 0 },
|
||||
})
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 下载模板
|
||||
const downloadFile = () => {
|
||||
if (props.templateName === '') {
|
||||
return ElMessage({ type: 'error', message: '未配置模板下载路径' })
|
||||
}
|
||||
let dom = document.createElement("a");
|
||||
dom.style.display = 'none'
|
||||
dom.target = '_blank'
|
||||
dom.href = `./templete/${props.templateName}`
|
||||
document.body.appendChild(dom);
|
||||
dom.click();
|
||||
document.body.removeChild(dom);
|
||||
}
|
||||
|
||||
const submitFile = () => {
|
||||
fileUpload.value.submit()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 文件上传之前判断
|
||||
* @param file 上传的文件
|
||||
* */
|
||||
const beforeExcelUpload = (file: UploadRawFile) => {
|
||||
uploadResult.value=''
|
||||
if(!['xls', 'xlsx'].includes(file.name.split('.').pop())){
|
||||
ElMessage.warning('上传文件只能是 xls / xlsx 格式' )
|
||||
return false;
|
||||
}
|
||||
if(props.uploadMaxSize>0&&file.size>(props.uploadMaxSize*1024*1024)){
|
||||
ElMessage.warning('上传文件大小不能超过'+props.uploadMaxSize+'MB!')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const uploadResult = ref('')
|
||||
|
||||
const emit = defineEmits(['updateCheck','resultHandleEvent'])
|
||||
const excelUploadSuccess = (response: any) => {
|
||||
const { message,status,data } = response;
|
||||
if(200===response.status){
|
||||
if(props.customResultHandle){
|
||||
dialogVisible.value=false
|
||||
emit('resultHandleEvent', data)
|
||||
}else{
|
||||
if(data.handleSuccess){
|
||||
uploadResult.value=data
|
||||
}else{
|
||||
ElMessage.error("Excel文件处理失败:"+data.message);
|
||||
}
|
||||
|
||||
}
|
||||
}else{
|
||||
if (status === 401) {
|
||||
ElMessage.error('未通过身份验证');
|
||||
}
|
||||
const temp = String(status)[0]
|
||||
if (temp === '9') {
|
||||
ElMessage.error(message || '系统出错');
|
||||
console.error('系统异常', response.data);
|
||||
return
|
||||
}else{
|
||||
ElMessage.error('系统异常');
|
||||
console.error('系统异常', response.data);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 上传错误提示
|
||||
const excelUploadError = (response: any) => {
|
||||
console.error(response);
|
||||
if (response.status === 401) {
|
||||
ElMessage.error('未通过身份验证');
|
||||
}else{
|
||||
ElMessage.error('文件上传服务异常');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
dialogVisible
|
||||
});
|
||||
onMounted(() => {
|
||||
uploadResult.value=''
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!-- 底部浮动-->
|
||||
<template >
|
||||
<div ref="bfRootRef" :key="isFloat? 'bottom-float':'bottom-fix'" :class="[isFloat ? 'bottom-float':'bottom-fix']" >
|
||||
<slot v-if="$slots['content']" name="content"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
const bfRootRef= ref()
|
||||
//组件传参
|
||||
let props = defineProps({
|
||||
height: { type: String,default:'50px' },//浮动层高度
|
||||
backgroundColor: { type: String,default:'#fff' },//浮动层背景色
|
||||
getWidthEvent: { type: Function },//获取浮动层宽度
|
||||
})
|
||||
|
||||
const isFloat=ref(false)
|
||||
const width=ref('')
|
||||
const initScrollHeight=ref(0)
|
||||
const initClientHeight=ref(0)
|
||||
const handleScroll = () => {
|
||||
let clientHeight=document.documentElement.clientHeight || window.innerHeight
|
||||
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
|
||||
|
||||
let temp=(scrollTop + clientHeight+bfRootRef.value.offsetHeight)
|
||||
isFloat.value=(temp >= initScrollHeight.value)?false:true
|
||||
}
|
||||
const windowChangesListener = () => {
|
||||
width.value=props.getWidthEvent()+'px'
|
||||
}
|
||||
onMounted(() => {
|
||||
width.value=props.getWidthEvent()+'px'
|
||||
initScrollHeight.value=document.documentElement.scrollHeight || document.body.scrollHeight
|
||||
initClientHeight.value=document.documentElement.clientHeight || window.innerHeight
|
||||
handleScroll()
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
window.addEventListener('resize', windowChangesListener)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bottom-float{
|
||||
position: fixed;
|
||||
height: v-bind('props.height');
|
||||
width: v-bind('width');
|
||||
bottom: 0vh;
|
||||
background-color: v-bind('props.backgroundColor');
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.bottom-fix{
|
||||
height: v-bind('props.height');
|
||||
width: v-bind('width');
|
||||
background-color: v-bind('props.backgroundColor');
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<!--日志显示组件-->
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="12"><el-text tag="b" size="large">变更前内容</el-text></el-col>
|
||||
<el-col :span="12"><el-text tag="b" size="large">变更后内容</el-text></el-col>
|
||||
</el-row>
|
||||
<el-row style="height: 90%;margin-top: 10px;">
|
||||
<el-col :span="12">
|
||||
<div class="logContent">
|
||||
<!-- 优先插槽 -->
|
||||
<slot v-if="$slots['beforeContent']" name="beforeContent"></slot>
|
||||
<!-- 优先处理JSON格式 -->
|
||||
<pre v-else-if="isJsonString(props.beforeContent)">{{ JSON.parse(props.beforeContent)}}</pre>
|
||||
<!-- 默认输出 -->
|
||||
<el-text v-else="props.beforeContent">{{ props.beforeContent }}</el-text>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="logContent">
|
||||
<!-- 优先插槽 -->
|
||||
<slot v-if="$slots['content']" name="content"></slot>
|
||||
<!-- 优先处理JSON格式 -->
|
||||
<pre v-else-if="isJsonString(props.content)">{{ JSON.parse(props.content)}}</pre>
|
||||
<!-- 默认输出 -->
|
||||
<el-text v-else="props.content">{{ props.content }}</el-text>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isJsonString } from '@/framework/utils/standard/dataHandleUtil.ts'
|
||||
|
||||
let props = defineProps({
|
||||
//变更前内容
|
||||
beforeContent: { type: String, default: '', },
|
||||
//变更后内容
|
||||
content: { type: String, default: '', },
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.logContent{
|
||||
border:1px solid;
|
||||
border-color: #707175;
|
||||
border-radius:5px;
|
||||
height: 100%;
|
||||
width: 90%;
|
||||
padding-top: 5px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
|
||||
<!-- 实时搜索下拉框组件 -->
|
||||
<template>
|
||||
<el-select v-model="value" remote :placeholder="placeholder" style="width: 100%;" filterable :remote-method="remoteMethod" :loading="loading" clearable
|
||||
:multiple="multiple" :collapse-tags="multiple" :collapse-tags-tooltip="multiple">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed} from 'vue'
|
||||
import { handleSelectData } from '@/framework/utils/standard/dataHandleUtil.ts'
|
||||
/**
|
||||
* 实时检索组件
|
||||
* SearchSelect
|
||||
* @author nielang
|
||||
* @description 用于实时检索的下拉框
|
||||
* @property {String} modelValue 使用v-model
|
||||
* @property {String} placeholder input的placeholder
|
||||
* @property {Boolean} multiple 是否支持多选
|
||||
* @property {Function} request 接口请求函数,默认请求参数{'keyword':'关键字'}
|
||||
* @property {Function} customRequestHandle 自定义请求参数处理
|
||||
* @property {Function} customResponseHandle 自定义响应结果处理
|
||||
* @property {Boolean} showOptionValue 选项中是否显示value属性,使用customResponseHandle后,该参数失效
|
||||
*/
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入关键字",
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
request:{
|
||||
type: Function,
|
||||
require: true
|
||||
},
|
||||
customRequestHandle: {
|
||||
type: Function,
|
||||
require: false
|
||||
},
|
||||
customResponseHandle: {
|
||||
type: Function,
|
||||
require: false
|
||||
},
|
||||
showOptionValue: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false);
|
||||
interface ListItem {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const options = ref<ListItem[]>([])
|
||||
const remoteMethod = (keyword: string) => {
|
||||
if (keyword) {
|
||||
loading.value = true
|
||||
if (props.request) {
|
||||
let params:any = {keyword:keyword}
|
||||
//自定义请求参数处理
|
||||
if(props.customRequestHandle)
|
||||
params=props.customRequestHandle(params)
|
||||
props.request(params).then((data: any) => {
|
||||
//自定义响应结果处理
|
||||
if(props.customRequestHandle){
|
||||
options.value=props.customResponseHandle(data)
|
||||
}else{
|
||||
options.value=handleSelectData(data,props.showOptionValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
} else {
|
||||
options.value = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<!--脱敏文本展示组件-->
|
||||
<template>
|
||||
<span :class="props.underline?'underline':''">
|
||||
<slot v-if="$slots.default"></slot>
|
||||
<template v-else-if="props.data" >
|
||||
<el-tooltip :content="props.aesDecrypt?decrypt(props.data,props.decryptKey):props.data" placement="bottom" effect="light">
|
||||
{{ desensitize(props.aesDecrypt?decrypt(props.data,props.decryptKey):props.data,props.type,props.custEvent) }}
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else> </template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { decrypt,desensitize,DesensitizationType } from '@/framework/utils/standard/security.ts'
|
||||
|
||||
let props = defineProps({
|
||||
data: { type: String, default: '', },//展示数据
|
||||
aesDecrypt:{type:Boolean,default: true},//是否aes解密
|
||||
decryptKey:{type:String,default: ''},//自定义密钥
|
||||
underline:{type:Boolean,default: false},//是否需要下划线
|
||||
type:DesensitizationType,//脱敏类型
|
||||
custEvent:{type: Function},//自定义脱敏方法
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.underline {
|
||||
border-bottom: 1px solid #CDD0D6;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!--文本展示组件-->
|
||||
<template>
|
||||
<span style="border-bottom: 1px solid #CDD0D6;width: 100%;display: block;">
|
||||
<slot v-if="$slots.default"></slot>
|
||||
<template v-else-if="props.data" >{{ props.data }}</template>
|
||||
<template v-else> </template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
let props = defineProps({
|
||||
data: { type: String, default: '', },
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
Reference in New Issue
Block a user