提交代码

This commit is contained in:
2025-04-12 14:54:02 +08:00
parent f7761e99a1
commit a2e89f5ea1
599 changed files with 194300 additions and 0 deletions

View File

@ -0,0 +1,67 @@
<template>
<el-breadcrumb class="breadcrumb-wrap" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumData" :key="item.path">
<!---不可点击-->
<span class="no-redirect" v-if="index === breadcrumData.length - 1">{{ item.meta.title }}</span>
<!---可点击项-->
<span class="redirect" v-else @click="onLinkClick(item)">{{ item.meta.title }}</span>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script setup>
import { useStore } from 'vuex'
import { watch, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
//生成数组数据
const breadcrumData = ref([]);
const getBreadcrumData = () => {
//当前路由的标准化路由纪录
breadcrumData.value = route.matched.filter(item => item.meta && item.meta.title)
}
//watch监听路由变化
watch(route, () => {
getBreadcrumData();
}, {
immediate: true
})
const store = useStore();
const linkHoverColor = ref(store.getters.cssVar.menuBg);
const router = useRouter();
const onLinkClick = (item) => {
router.push(item.path)
}
</script>
<style lang="scss" scoped>
.breadcrumb-wrap {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
.redirect {
color: #666;
font-weight: 600;
cursor: pointer;
}
.redirect:hover {
// 将来需要进行主题替换,所以这里不去写死样式
color: v-bind(linkHoverColor);
}
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div>
<template v-for="(item, index) in options">
<template v-if="values.includes(item.value)">
<span
v-if="item.elTagType == 'default' || item.elTagType == '' || tag == false"
:key="item.value"
:index="index"
:class="item.elTagType"
>{{ item.label }}</span>
<el-tag
v-else
:disable-transitions="true"
:key="item.value + ''"
:index="index"
:type="item.elTagType === 'primary' ? '' : item.elTagType"
:class="item.elTagType"
>{{ item.label }}</el-tag
>
</template>
</template>
</div>
</template>
<script setup>
import { computed } from "vue";
const props = defineProps({
// 数据
options: {
type: Array,
default: null
},
tag: false,
value: [Number, String, Array]
});
const values = computed(() => {
if (props.value !== null && typeof props.value !== "undefined") {
return Array.isArray(props.value) ? props.value : [String(props.value)];
} else {
return [];
}
});
</script>
<style scoped>
.el-tag + .el-tag {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,91 @@
<template>
<el-form ref="formRef" :model="groupData" :rules="rules">
<el-form-item
:label-width="LABEL_WIDTH"
prop="modelValue"
label="邮箱"
>
<el-input
v-bind="$attrs"
v-model="groupData.modelValue"
@input="onInput()"
@blur="validate()"
max="11"
></el-input>
</el-form-item>
</el-form>
</template>
<script setup>
import { LABEL_WIDTH } from '@/constant';
import { ref, defineProps, defineEmits, defineExpose } from 'vue'
const formRef = ref(null);
const props = defineProps({
isRequired: {
type: Boolean,
default: false
}
})
// import { validateIdentity } from './rules'
const groupData = ref({
modelValue: ''
});
const emits = defineEmits(['update:modelValue']);
const onInput = (e) => {
emits('update:modelValue', groupData.value.modelValue)
}
const isBX = ref(false)
const validateIdentity = () => {
return (rule, value, callback) => {
const reg = /^([a-zA-Z0-9]+[-_\.]?)+@[a-zA-Z0-9]+\.[a-z]+$/
if (!value) {
} else {
if (!reg.test(value)) {
return callback(new Error('请输入正确的邮箱地址'));
} else {
callback();
}
}
};
};
// 验证规则
const rules = ref({
modelValue: [
{
required: props.isRequired,
message: '请输入邮箱',
trigger: 'blur'
},
{
trigger: 'blur',
validator: validateIdentity()
}
]
})
// 校验是否 符合规则 。 把当前组件名通过 emits分发出去
const validate = () => {
formRef.value.validate((valid) => {
if (valid) {
emits('validateStatus', { 'Email': true })
} else {
emits('validateStatus', { 'Email': false })
}
})
if (!props.isRequired && groupData.value.modelValue.length === 0) {
emits('validateStatus', { 'Email': true })
}
}
const childMethod = () => {
groupData.value.modelValue = ""
}
// 主动暴露childMethod方法
defineExpose({ childMethod })
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,68 @@
<template>
<el-form ref="formRef" :model="groupData" :rules="rules">
<el-form-item :label-width="LABEL_WIDTH" prop="modelValue" label="身份证号">
<el-input
v-bind="$attrs"
v-model="groupData.modelValue"
@input="onInput()"
@blur="validate()"
></el-input>
</el-form-item>
</el-form>
</template>
<script setup>
import { LABEL_WIDTH } from "@/constant";
import { ref, defineProps, defineEmits, defineExpose } from "vue";
import { validateIdentity } from "./rules";
const props = defineProps({
isRequired: {
type: Boolean,
default: false
}
});
const groupData = ref({
modelValue: ""
});
const formRef = ref(null);
const emits = defineEmits(["videoListShowFn"]);
const onInput = (e) => {
emits("update:modelValue", groupData.value.modelValue);
};
// 验证规则
const rules = ref({
modelValue: [
{
required: props.isRequired,
message: "请输入身份证",
trigger: "blur"
},
{
trigger: "change",
validator: validateIdentity()
}
]
});
// 校验是否 符合规则 。 把当前组件名通过 emits分发出去
const validate = () => {
formRef.value.validate((valid) => {
if (valid) {
emits("validateStatus", { IdentityCard: true });
} else {
emits("validateStatus", { IdentityCard: false });
}
});
if (!props.isRequired && groupData.value.modelValue.length === 0) {
emits("validateStatus", { IdentityCard: true });
}
};
const childMethod = () => {
groupData.value.modelValue = "";
};
// 主动暴露childMethod方法
defineExpose({ childMethod });
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,79 @@
export const validateIdentity = () => {
return (rule, value, callback) => {
if (!value) {
// return callback(new Error('身份证号不能为空'));
} else if (!/(^\d{15}$)|(^\d{17}(\d|X|x)$)/.test(value)) {
callback(new Error('输入的身份证长度或格式错误'));
}
//身份证城市
var aCity = {
11: '北京',
12: '天津',
13: '河北',
14: '山西',
15: '内蒙古',
21: '辽宁',
22: '吉林',
23: '黑龙江',
31: '上海',
32: '江苏',
33: '浙江',
34: '安徽',
35: '福建',
36: '江西',
37: '山东',
41: '河南',
42: '湖北',
43: '湖南',
44: '广东',
45: '广西',
46: '海南',
50: '重庆',
51: '四川',
52: '贵州',
53: '云南',
54: '西藏',
61: '陕西',
62: '甘肃',
63: '青海',
64: '宁夏',
65: '新疆',
71: '台湾',
81: '香港',
82: '澳门',
91: '国外'
};
if (!aCity[parseInt(value.substr(0, 2))]) {
callback(new Error('身份证地区非法'));
}
// 出生日期验证
var sBirthday = (
value.substr(6, 4) +
'-' +
Number(value.substr(10, 2)) +
'-' +
Number(value.substr(12, 2))
).replace(/-/g, '/'),
d = new Date(sBirthday);
if (
sBirthday !==
d.getFullYear() + '/' + (d.getMonth() + 1) + '/' + d.getDate()
) {
callback(new Error('身份证上的出生日期非法'));
}
// 身份证号码校验
var sum = 0,
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2],
codes = '10X98765432';
for (var i = 0; i < value.length - 1; i++) {
sum += value[i] * weights[i];
}
var last = codes[sum % 11]; //计算出来的最后一位身份证号码
if (value[value.length - 1] !== last) {
callback(new Error('输入的身份证号非法'));
}
callback();
};
};

View File

@ -0,0 +1,86 @@
<template>
<el-form ref="formRef" :model="groupData" :rules="rules">
<el-form-item :label-width="LABEL_WIDTH" prop="modelValue" label="手机号">
<el-input v-bind="$attrs" v-model="groupData.modelValue" @input="onInput()" @blur="validate()" max="11">
</el-input>
</el-form-item>
</el-form>
</template>
<script setup>
import { LABEL_WIDTH } from '@/constant';
import { ref, defineProps, defineEmits, defineExpose } from 'vue'
const formRef = ref(null);
const props = defineProps({
isRequired: {
type: Boolean,
default: false
}
})
const isOk = ref()
const groupData = ref({
modelValue: ''
});
const emits = defineEmits(['update:modelValue']);
const onInput = (e) => {
emits('update:modelValue', groupData.value.modelValue)
}
const isBX = ref(false)
const validateIdentity = () => {
return (rule, value, callback) => {
if (!value) {
} else {
const reg = /^1[3|4|5|7|8][0-9]\d{8}$/
if (reg.test(value)) {
callback();
} else {
return callback(new Error('请输入正确的手机号'));
}
}
};
};
// 验证规则
const rules = ref({
modelValue: [
{
required: props.isRequired,
message: '请输入手机号',
trigger: 'blur'
},
{
min: 11,
message: '手机号格式不正确',
trigger: 'blur'
},
{
trigger: 'change',
validator: validateIdentity()
}
]
})
// 校验是否 符合规则 。 把当前组件名通过 emits分发出去
const validate = () => {
formRef.value.validate((valid) => {
if (valid) {
emits('validateStatus', { Phone: true })
isOk.value = true
} else {
emits('validateStatus', { Phone: false })
isOk.value = false
}
})
if (!props.isRequired && groupData.value.modelValue.length === 0) {
emits('validateStatus', { Phone: true })
isOk.value = true
}
}
const childMethod = () => {
groupData.value.modelValue = ""
}
// 主动暴露childMethod方法
defineExpose({ childMethod })
</script>

View File

@ -0,0 +1,307 @@
<template>
<div class="form-item-box zj-addressSelect-wrap" :style="{ width: width }">
<el-select :placeholder="placeholder" :clearable="true" v-model="value" popper-class="adderss-select"
@clear="handleClear">
<el-option value="1" style="display: none"></el-option>
<el-input v-if="filterable" v-model="filterText" style="margin-bottom: 5px; font-size: 12px"
:prefix-icon="Search" />
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane name="province" :label="province ? province : '请选择'">
<div class="citylist">
<ul>
<li @click="chooseProvince(item)" v-for="item in provinceList" :key="item.code"
:class="{ selected: provinceId == item.code }">
{{ item.name }}
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane name="city" v-if="cityList.length > 0" :label="city ? city : '请选择'">
<div class="citylist">
<ul>
<li @click="chooseCity(item)" v-for="item in cityList" :class="{ selected: cityId == item.code }"
:key="item.code">
{{ item.name }}
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane name="area" v-if="areaList.length > 0" :label="area ? area : '请选择'">
<div class="citylist">
<ul>
<li @click="chooseArea(item)" v-for="item in areaList" :key="item.code"
:class="{ selected: areaId == item.code }">
{{ item.name }}
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane name="street" v-if="streetList.length > 0" :label="street ? street : '请选择'">
<div class="citylist">
<ul>
<li @click="chooseStreet(item)" v-for="item in streetList" :key="item.code"
:class="{ selected: streetId == item.code }">
{{ item.name }}
</li>
</ul>
</div>
</el-tab-pane>
</el-tabs>
</el-select>
<!-- <el-icon class="errorIcon">
<circle-close-filled />
</el-icon>
<el-icon class="checkIcon">
<circle-check-filled />
</el-icon> -->
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { nextTick, ref, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
const emits = defineEmits(["handleChange"]); //子组件向父组件事件传递
const props = defineProps({
placeholder: {
default: "选择地址",
type: String
},
filterable: {
default: false,
type: Boolean
},
address: {
default: () => [],
type: Array
},
defaultConf: {
type: Object,
default: () => ({
children: "",
label: "",
id: ""
})
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const filterText = ref("");
const activeName = ref("province");
const value = ref("");
const valueId = ref([]);
const province = ref("");
const provinceId = ref("");
const provinceList = ref([]);
provinceList.value = require("@/constant/pca-code.json");
const city = ref("");
const cityId = ref("");
const cityList = ref([]);
const area = ref("");
const areaId = ref("");
const areaList = ref([]);
const street = ref("");
const streetId = ref("");
const streetList = ref([]);
const provinceListFilter = ref([]);
provinceListFilter.value = require("@/constant/pca-code.json");
const cityListFilter = ref([]);
const areaListFilter = ref([]);
const streetListFilter = ref([]);
nextTick(() => {
if (props.address.length > 0) {
init(props.address);
}
});
watch(filterText, (val) => {
filterNode(val);
});
const handleClick = () => {
filterText.value = "";
filterNode("");
};
const filterNode = (val) => {
if (activeName.value === "province") {
if (val !== "") {
provinceList.value = provinceListFilter.value.filter(
(item) => item[props.defaultConf.label].indexOf(val) !== -1
);
} else {
provinceList.value = provinceListFilter.value;
}
} else if (activeName.value === "city") {
if (val !== "") {
cityList.value = cityListFilter.value.filter(
(item) => item[props.defaultConf.label].indexOf(val) !== -1
);
} else {
cityList.value = cityListFilter.value;
}
} else if (activeName.value === "area") {
if (val !== "") {
areaList.value = areaListFilter.value.filter(
(item) => item[props.defaultConf.label].indexOf(val) !== -1
);
} else {
areaList.value = areaListFilter.value;
}
} else if (activeName.value === "street") {
if (val !== "") {
streetList.value = streetListFilter.value.filter(
(item) => item[props.defaultConf.label].indexOf(val) !== -1
);
} else {
streetList.value = streetListFilter.value;
}
}
};
const chooseProvince = (e) => {
province.value = e[props.defaultConf.label];
provinceId.value = e[props.defaultConf.id];
value.value = province.value;
valueId.value = [provinceId.value];
emits("handleChange", valueId.value);
city.value = "";
cityId.value = "";
cityList.value = [];
areaList.value = [];
streetList.value = [];
filterText.value = "";
provinceList.value.forEach((el) => {
if (el[props.defaultConf.id] === e[props.defaultConf.id]) {
if (el[props.defaultConf.children]) {
activeName.value = "city";
cityList.value = el[props.defaultConf.children];
cityListFilter.value = el[props.defaultConf.children];
}
}
});
};
const chooseCity = (e) => {
city.value = e[props.defaultConf.label];
cityId.value = e[props.defaultConf.id];
area.value = "";
areaId.value = "";
areaList.value = [];
streetList.value = [];
filterText.value = "";
value.value = province.value + "/" + city.value;
valueId.value = [provinceId.value, cityId.value];
emits("handleChange", valueId.value);
cityList.value.forEach((el) => {
if (el[props.defaultConf.id] === e[props.defaultConf.id]) {
if (el[props.defaultConf.children]) {
activeName.value = "area";
areaList.value = el[props.defaultConf.children];
areaListFilter.value = el[props.defaultConf.children];
}
}
});
};
const chooseArea = (e) => {
area.value = e[props.defaultConf.label];
areaId.value = e[props.defaultConf.id];
street.value = "";
streetId.value = "";
streetList.value = [];
filterText.value = "";
value.value = province.value + "/" + city.value + "/" + area.value;
valueId.value = [provinceId.value, cityId.value, areaId.value];
emits("handleChange", valueId.value);
areaList.value.forEach((el) => {
if (el[props.defaultConf.id] === e[props.defaultConf.id]) {
if (el[props.defaultConf.children]) {
streetList.value = el[props.defaultConf.children];
activeName.value = "street";
streetListFilter.value = el[props.defaultConf.children];
}
}
});
};
const chooseStreet = (e) => {
street.value = e[props.defaultConf.label];
streetId.value = e[props.defaultConf.id];
value.value =
province.value + "/" + city.value + "/" + area.value + "/" + street.value;
valueId.value = [
provinceId.value,
cityId.value,
areaId.value,
streetId.value
];
emits("handleChange", valueId.value);
};
const handleClear = () => {
activeName.value = "province";
value.value = "";
valueId.value = [];
province.value = "";
provinceId.value = "";
city.value = "";
cityId.value = "";
cityList.value = [];
area.value = "";
areaId.value = "";
areaList.value = [];
street.value = "";
streetId.value = "";
streetList.value = [];
cityListFilter.value = [];
areaListFilter.value = [];
streetListFilter.value = [];
filterText.value = "";
filterNode("");
emits("handleChange", []);
};
const treeValueFind = (tree, arr, newArr = []) => {
tree.forEach((el) => {
if (el[props.defaultConf.id] === arr) {
newArr.push(el);
}
if (el[props.defaultConf.children]) {
treeValueFind(el[props.defaultConf.children], arr, newArr);
}
});
return newArr;
};
const init = (data) => {
let obj1 = treeValueFind(provinceList.value, data[0])[0];
province.value = obj1[props.defaultConf.label];
provinceId.value = obj1[props.defaultConf.id];
cityList.value = obj1[props.defaultConf.children];
cityListFilter.value = obj1[props.defaultConf.children];
value.value = province.value;
if (data[1]) {
let obj2 = treeValueFind(provinceList.value, data[1])[0];
city.value = obj2[props.defaultConf.label];
cityId.value = obj2[props.defaultConf.id];
areaList.value = obj2[props.defaultConf.children];
areaListFilter.value = obj2[props.defaultConf.children];
value.value = province.value + "/" + city.value;
}
if (data[2]) {
let obj3 = treeValueFind(provinceList.value, data[2])[0];
area.value = obj3[props.defaultConf.label];
areaId.value = obj3[props.defaultConf.id];
streetList.value = obj3[props.defaultConf.children];
streetListFilter.value = obj3[props.defaultConf.children];
value.value = province.value + "/" + city.value + "/" + area.value;
}
if (data[3]) {
let obj4 = treeValueFind(provinceList.value, data[3])[0];
street.value = obj4[props.defaultConf.label];
streetId.value = obj4[props.defaultConf.id];
value.value =
province.value + "/" + city.value + "/" + area.value + "/" + street.value;
}
};
</script>
<style lang="scss" scoped>
.zj-addressSelect-wrap {
::v-deep .el-select {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<!--选择图标-->
<div class="form-item-box choose-icon-zj" :style="{ width: width }">
<el-autocomplete v-bind="$attrs" v-model="modelValue" :fetch-suggestions="querySearch"
popper-class="choose-icon-zj-autocomplete" :placeholder="placeholder" @change="onInput" @select="handleSelect">
<template #prefix>
<SvgIcon :icon="modelValue"></SvgIcon>
</template>
<template #default="{ item }">
<SvgIcon :icon="item.link"></SvgIcon>
<div class="value">{{ item.value }}</div>
</template>
</el-autocomplete>
<!-- <el-icon class="errorIcon">
<circle-close-filled />
</el-icon>
<el-icon class="checkIcon">
<circle-check-filled />
</el-icon> -->
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from "@/constant";
import { ref, defineProps, defineEmits, defineExpose, onMounted } from "vue";
const props = defineProps({
placeholder: {
default: "请输入图标名称",
type: String
},
modelValue: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const links = ref([]);
const querySearch = (queryString, cb) => {
const results = queryString
? links.value.filter(createFilter(queryString))
: links.value;
cb(results);
};
const createFilter = (queryString) => {
return (restaurant) => {
return (
restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
};
const handleSelect = (item) => {
emits("update:modelValue", item.value);
};
const handleIconClick = (ev) => {
};
const loadAll = () => {
const svgRequire = require.context("@/icons/svg", false, /\.svg$/);
const re = svgRequire.keys().map((svgIcon) => {
const iconName = svgIcon.split("/")[1];
const prefixIconName = iconName.split(".")[0];
return { value: prefixIconName, link: prefixIconName };
});
return re;
};
const iconListShow = ref(false);
const showIconList = () => {
iconListShow.value = true;
};
onMounted(() => {
links.value = loadAll();
});
const emits = defineEmits(["update:modelValue"]);
const onInput = (e) => {
emits("update:modelValue", e);
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,32 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-date-picker style="width:100%" v-model="modelValue" type="date" v-bind="$attrs" @change="onInput" :placeholder="placeholder" value-format="YYYY-MM-DD"/>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from "@/constant";
import { ref, defineProps, defineEmits, defineExpose } from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请填写",
type: String
},
modelValue: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
},
});
const emits = defineEmits(["update:modelValue"]);
const onInput = (e) => {
emits("update:modelValue", e);
};
</script>

View File

@ -0,0 +1,119 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-cascader
style="width: 100%"
class="el-cascader-zj"
:show-all-levels="false"
clearable
filterable
:placeholder="modelValue ? placeholder : '请选择部门'"
:options="tableData"
v-model="oldmodelValue"
@change="handleChange"
:props="endProps"
/>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from "@/constant";
import { qcckPost, qcckGet, qcckFlvGet } from "@/api/qcckApi.js";
import {
ref,
defineProps,
defineEmits,
defineExpose,
computed,
onMounted,
watch
} from "vue";
import { selectDeptPage } from "@/api/user-manage";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择",
type: String
},
multiple: {
default: false,
type: Boolean
},
isAll: {
default: 100,
type: Number
},
modelValue: {
type: Array || String,
default: []
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const modelShow = ref(false);
const oldmodelValue = ref([]);
const listQuery = ref({
deptname: "",
deptcode: "",
parentid: ""
});
const depList = ref([])
//配置项
const endProps = {
children: "childDeptList",
value: "orgCode",
label: "orgName",
checkStrictly: true,
multiple: props.multiple,
lazy: true,
lazyLoad(node, resolve) {
listQuery.value.parentid = node.data.id;
selectDeptPage(listQuery.value).then((res) => {
depList.value = depList.value.concat(res)
//处理部门是否包含下级
for (let i = 0; i < res.length; i++) {
res[i].leaf = !res[i].hasChildren;
}
resolve(res);
});
}
};
const tableData = ref([]);
const getSysMenuTree = async () => {
const res = await selectDeptPage(listQuery.value);
tableData.value = res;
depList.value = res
};
onMounted(() => {
getSysMenuTree();
});
watch(
() => props.modelValue,
(val) => {
console.log(val,'val');
oldmodelValue.value = val;
},
{ deep: true, immediate: true }
);
const emits = defineEmits(["update:modelValue",'getDepValue']);
const handleChange = (e) => {
console.log(e,'e');
if (props.multiple === true) {
const data = e.map((item) => {return item[item.length - 1];});
emits("update:modelValue", data);
} else {
const data = e ? e[e.length - 1] : "";
emits("update:modelValue", data);
let obj = depList.value.find(item=>{ return item.orgCode == data})
emits("getDepValue", obj);
}
};
</script>
<style lang="scss" scoped>
.el-cascader-zj {
width: 100%;
}
</style>

View File

@ -0,0 +1,158 @@
<template>
<div class="departmentTree-box" :style="{ width: width, height: '100%' }">
<div class="depar_hear">
<el-input
v-model="listQuery.deptname"
v-if="filterable"
clearable
:debounce="500"
@input="filterTextChange"
placeholder="请输入筛选条件"
/>
</div>
<div class="depar_foot">
<el-tree
ref="treeRef"
class="filter-tree"
:props="endProps"
lazy
:load="loadNode"
@node-click="nodeClick"
:filter-node-method="filterNode"
:data="treeData"
/>
</div>
</div>
</template>
<script setup>
import { debounce } from "lodash";
import { getItem } from "@/utils/storage";
import { COMPONENT_WIDTH } from "@/constant";
import { ref, defineProps, defineEmits, watch, computed, onMounted } from "vue";
import { selectDeptPage, getAllChildDeptList } from "@/api/user-manage";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择",
type: String
},
multiple: {
default: false,
type: Boolean
},
filterable: {
default: false,
type: Boolean
},
modelValue: {
type: Number
},
width: {
default: COMPONENT_WIDTH,
type: String
},
isBmId:{
type:Boolean,
default:false
}
});
const listQuery = ref({
deptname: "",
deptcode: "",
parentid: ""
});
const treeRef = ref(null);
const node_had = ref([]);
const resolve_had = ref([]);
//防抖处理
const filterTextChange = debounce(inputChange, 500);
onMounted(() => {});
//获取部门数据
function getTreeData() {
selectDeptPage(listQuery.value).then((res) => {
for (let i = 0; i < res.length; i++) {
res[i].leaf = !res[i].hasChildren;
}
treeData.value = res;
});
}
//搜索查询
function inputChange(e) {
selectDeptPage(listQuery.value).then((res) => {
treeData.value = res;
});
}
watch(
() => listQuery.value.deptname,
(val) => {
treeRef.value.filter("tree", val);
}
);
const endProps = {
children: "childDeptList",
value: "id",
label: "orgName",
isLeaf: "leaf"
};
const treeData = ref([]);
//懒加载方法
async function loadNode(node, resolve) {
listQuery.value.parentid = node.data.id;
if (node.level === 0) {
node_had.value = node;
resolve_had.value = resolve;
getTreeData();
}
if (node.level >= 1) {
selectDeptPage(listQuery.value).then((res) => {
for (let i = 0; i < res.length; i++) {
res[i].leaf = !res[i].hasChildren;
}
resolve(res);
});
}
}
const filterNode = (value, data) => {
if (!value) return true;
return data.orgName.includes(value);
};
const nodeClick = (node) => {
if(props.isBmId){
emits("update:modelValue", node.id);
}else{
emits("update:modelValue", node.orgCode);
}
};
const emits = defineEmits(["update:modelValue"]);
const handleChange = (e) => {
if (props.multiple === true) {
const data = e.map((item) => {
return item[item.length - 1];
});
emits("update:modelValue", data);
} else {
const data = e[0];
emits("update:modelValue", data);
}
};
</script>
<style lang="scss" scoped>
.depar_hear {
height: 32px;
}
.depar_foot {
height: calc(100% - 32px);
overflow: auto;
width: 280px;
width: 100%;
min-width: 300px;
}
.departmentTree-box {
overflow: auto;
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<el-dialog
title="导出预览"
width="1400px"
:model-value="modelValue"
@close="closed"
>
<el-table :data="data" :id="myId" style="width: 100%" border>
<el-table-column label="序号" type="index" align="center" width="80" />
<el-table-column
v-for="(item, index) in tabOption"
:key="index + 'tab'"
:prop="item.prop"
:label="item.label"
align="center"
>
<template v-if="item.dict" #default="{ row }">
<dict-tag :options="item.dict" :value="row[item.prop]" :tag="false" />
</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="closed">取消</el-button>
<el-button type="primary" @click="exportExcel">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { defineProps, ref, onMounted, getCurrentInstance } from "vue";
import FileSaver from "file-saver";
import * as XLSX from "xlsx";
const tabRef = ref(null);
const props = defineProps({
modelValue: {
type: Boolean,
required: true
},
tabOption: {
type: Array,
require: []
},
data: {
type: Array,
required: []
},
myId: {
type: String,
require: ""
},
title: {
type: String,
require: ""
}
});
const emits = defineEmits(["update:modelValue"]);
const closed = () => {
emits("update:modelValue", false);
};
const { proxy } = getCurrentInstance();
function exportExcel() {
let xlsxParam = { raw: true };
let wb = XLSX.utils.table_to_book(
document.querySelector("#" + props.myId),
xlsxParam
);
let wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array"
});
closed();
try {
FileSaver.saveAs(
new Blob([wbout], { type: "application/octet-stream" }),
`${props.title}.xlsx`
);
} catch (e) {
if (typeof console !== "undefined") console.log(e, wbout);
}
return wbout;
}
</script>
<style>
</style>

View File

@ -0,0 +1,80 @@
<template>
<div class="form-item-box zj-email-wrap" :style="{ width: width }">
<el-autocomplete v-model="email" v-bind="$attrs" :placeholder="placeholder" :fetch-suggestions="querySearch"
:trigger-on-focus="false" class="inline-input" @select="handleSelect" @input="onInput" />
<!-- <el-icon class="errorIcon">
<circle-close-filled />
</el-icon>
<el-icon class="checkIcon">
<circle-check-filled />
</el-icon> -->
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { ref, defineProps, defineEmits, defineExpose, onMounted } from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请填写邮箱",
type: String
},
email: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const emits = defineEmits(["update:email"]);
const onInput = (e) => {
emits("update:email", e);
};
const restaurants = ref([]);
const createFilter = (queryString) => {
return (item) => {
return item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
};
};
const querySearch = (queryString, callback) => {
let results = JSON.parse(JSON.stringify(restaurants.value)); //把数组的浅复制换成深复制
if (queryString.indexOf("@") > -1) {
results.length = 0;
callback(results);
return false;
}
for (let item in results) {
results[item].value = queryString + "" + restaurants.value[item].value;
}
callback(results);
};
const loadAll = () => {
return [
{ value: "@qq.com" },
{ value: "@mosty.com" },
{ value: "@163.com" },
{ value: "@outlook.com" },
{ value: "@sohu.com" }
];
};
const handleSelect = (item) => {
emits("update:email", item.value);
};
onMounted(() => {
restaurants.value = loadAll();
});
</script>
<style lang="scss" scoped>
.zj-email-wrap {
::v-deep .el-autocomplete {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,247 @@
<template>
<div class="main-box">
<div class="file_box" v-for="(item, index) in fileList" :key="index">
<div class="show_file" :style="{ width: width, height: width }">
<div class="icon_box_y" :style="{ width: width, height: width }">
<svg-icon :icon="getSuffix(item.url)" />
<span class="file_name_box">{{ item.name }}</span>
</div>
<div class="load_and_del" :style="{ width: width, height: width }">
<el-icon class="load_and_del_icon" :size="18" color="#aaaaaa">
<DeleteFilled v-if="props.isEdit" @click="delFile(index)" />
</el-icon>
<el-icon class="load_and_del_icon" :size="18" color="#aaaaaa">
<Download @click="downloadFile(item.url)" />
</el-icon>
</div>
</div>
</div>
<div class="file_box" v-if="fileList.length != props.limit && props.isEdit">
<div class="upload_img" :style="{ width: width, height: width }">
<div class="icon_box" :style="{ width: width, height: width }">
<el-icon :size="30" color="#aaaaaa">
<Plus />
</el-icon>
</div>
<div class="file_box_item" :style="{ width: width, height: width }">
<input
type="file"
:style="{ width: width, height: width }"
class="file_input"
id="file"
@change="fileChange"
/>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
ref,
defineProps,
defineEmits,
defineExpose,
computed,
onMounted
} from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import request from "@/utils/request";
import axios from "axios";
const props = defineProps({
modelValue: {
type: String,
default: ""
},
width: {
type: String,
default: "150px"
},
limit: {
type: Number,
default: 1
},
isEdit: {
type: Boolean,
default: false
}
});
const fileList = ref([]);
const count = ref(0); // 上传的数量
const emits = defineEmits(["update:modelValue", "handleChange"]);
//获取后缀
const getSuffix = (fileName) => {
let suffix = "";
try {
suffix = fileName.substr(fileName.lastIndexOf(".") + 1, 4);
if (suffix.indexOf("?") !== -1) suffix = suffix.replaceAll("?", "");
} catch (err) {
suffix = "";
return "OTHER";
}
// fileName无后缀返回 false
if (!suffix) return "";
// 图片格式
var imglist = ["png", "jpg", "jpeg", "bmp", "gif"];
if (imglist.includes(suffix)) return "IMG";
//txt
if (suffix === "txt") return "TXT";
//excel XLS
const excelist = ["xls", "xlsx"];
if (excelist.includes(suffix)) return "XLS";
// 匹配 word
var wordlist = ["doc", "docx"];
if (wordlist.includes(suffix)) return "DOC";
//pdf
if (suffix === "pdf") return "PDF";
//视频 音频
var videolist = [
"mp4",
"m2v",
"mkv",
"rmvb",
"wmv",
"avi",
"flv",
"mov",
"m4v"
];
if (videolist.includes(suffix)) return "VIDEO";
var musiclist = ["mp3", "wav", "wmv"];
if (musiclist.includes(suffix)) return "MUSIC";
var pptlist = ["ppt", "pptx"];
if (pptlist.includes(suffix)) return "PPT";
//压缩包
var yslist = ["7z", "rar", "zip", "apz", "ar", "hpk", "hyp", "hbc2"];
if (yslist.includes(suffix)) return "YS";
//否则返回other
return "OTHER";
};
// 删除
function delFile(index) {
fileList.value = fileList.value.filter((item, i) => {
return i !== index;
});
}
// 文件下载
function downloadFile(url) {
window.open('/mosty-api/mosty-base/minio/image/download/'+url, "_blank");
}
// 选择文件
function fileChange(e) {
let file = document.getElementById("file").files[0];
let name = file.name;
let formData = new FormData();
formData.append("file", file);
axios
.post("/mosty-api/mosty-base/minio/image/upload/id", formData, {
"Content-type": "multipart/form-data"
})
.then((res) => {
if (res.status == 200 && res.data && res.data.code === 10000) {
let url = res.data.data;
let f = {
url: url,
name: name
};
fileList.value.push(f);
count.value = count.value + 1;
let list = fileList.value.map((item) => {
return item.url;
});
emits("handleChange", JSON.stringify(list));
} else {
ElMessage.warning("文件上传失败");
}
});
}
onMounted(() => {
if (props.modelValue) {
let list = JSON.parse(props.modelValue);
list.forEach((item, index) => {
let temp = {
url: item,
name: "文件" + (index + 1)
};
fileList.value.push(temp);
});
count.value = list.length;
}
});
</script>
<style scoped lang="scss">
.main-box {
width: 100%;
.file_box {
background-color: #112b63;
border: 1px dashed #4579b5;
margin: 10px 0 0 10px;
border-radius: 5px;
position: relative;
.show_file {
overflow: hidden;
box-sizing: border-box;
&:hover > .load_and_del {
display: block;
}
.load_and_del {
display: none;
position: absolute;
top: 0;
left: 0;
.load_and_del_icon {
cursor: pointer;
margin-left:5px;
margin-top: 5px;
}
}
.icon_box_y {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.file_name_box {
margin-top: 10px;
width: 80%;
line-height: 30px;
height: 30px;
overflow: hidden;
text-align: center;
}
}
::v-deep .svg-icon {
font-size: 48px;
}
}
.upload_img {
position: relative;
.icon_box {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
z-index: 1;
}
.file_box_item {
position: absolute;
opacity: 0;
z-index: 2;
top: 0;
left: 0;
.file_input {
cursor: pointer;
}
}
.file_box_item_show {
position: absolute;
top: 0;
left: 0;
}
}
}
}
</style>

View File

@ -0,0 +1,198 @@
<template>
<div class="form-item-box zk-frameWork-wrap" :style="{ width: width }">
<el-select
:model-value="frameWork"
:placeholder="placeholder"
@clear="handleClear"
@change="selectChange"
:clearable="clearable"
v-bind="$attrs"
:multiple="multiple"
popper-class="frameWork-select"
>
<el-option value="1" style="display: none"></el-option>
<el-input
v-if="filterable"
v-model="filterText"
style="width: 96% !important; margin: 0 2%; font-size: 12px"
:prefix-icon="Search"
/>
<div class="alllist">
<el-tree
ref="tree"
:data="treeData"
:props="defaultConf"
default-expand-all
:filter-node-method="filterNode"
:show-checkbox="multiple"
:node-key="nodeKey"
@check-change="handleCheckChange"
@node-click="clickNode"
/>
</div>
</el-select>
<!-- <el-icon class="errorIcon"><circle-close-filled /></el-icon>
<el-icon class="checkIcon"><circle-check-filled /></el-icon> -->
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { nextTick, ref, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
const emits = defineEmits(["handleChange"]); //子组件向父组件事件传递
const props = defineProps({
clearable: {
default: false,
type: Boolean
},
multiple: {
default: false,
type: Boolean
},
filterable: {
default: false,
type: Boolean
},
treeData: {
default: () => [],
type: Array
},
placeholder: {
default: "选择组织机构",
type: String
},
defaultConf: {
type: Object,
default: () => ({
children: "children",
label: "label"
})
},
frameWork: {
type: [String, Array],
default: ""
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const nodeKey = "id";
const filterText = ref("");
const mineStatusValue = ref([]); //选中状态存贮
nextTick(() => {
if (props.frameWork) {
if (typeof props.frameWork === "string") {
//传过来的默认值类型
if (props.frameWork) {
tree.value.setCheckedKeys([props.frameWork], false);
}
} else {
if (props.frameWork.length > 0) {
tree.value.setCheckedKeys(props.frameWork, false);
}
}
mineStatusValue.value = treeValueFind(props.treeData, props.frameWork);
}
});
const tree = ref(null);
let deferTimer;
watch(filterText, (val) => {
tree.value.filter(val);
});
const filterNode = (value, data) => {
if (!value) return true;
return data[props.defaultConf.label].indexOf(value) !== -1;
};
//多选选中
const handleCheckChange = () => {
const res = tree.value.getCheckedNodes(true, true); // 这里两个true1. 是否只是叶子节点 2. 是否包含半选节点
mineStatusValue.value = res;
clearTimeout(deferTimer);
deferTimer = setTimeout(() => {
emits("handleChange", res);
}, 200);
};
//节点选择
const clickNode = (data, node, obj) => {
if (props.multiple) {
// 多选不执行
const index = mineStatusValue.value.findIndex((d) => d.id === data.id);
if (index > -1) {
tree.value.setChecked(data, false);
} else {
tree.value.setChecked(data, true);
}
return;
}
if (!data.children) {
mineStatusValue.value.push(data.id);
tree.value.setCheckedKeys([data.id], false);
}
};
//select值变化
const selectChange = (e) => {
if (!props.multiple || !props.treeData.length) {
return false;
}
const arrNew = [];
const dataLength = mineStatusValue.value.length;
const eleng = e.length;
for (let i = 0; i < dataLength; i++) {
for (let j = 0; j < eleng; j++) {
if (e[j] === mineStatusValue.value[i][props.defaultConf.label]) {
arrNew.push(mineStatusValue.value[i][nodeKey]);
break;
}
}
}
setTimeout(() => {
tree.value.setCheckedKeys(arrNew, false);
}, 200);
};
//select clear
const handleClear = (e) => {
mineStatusValue.value = [];
tree.value.setCheckedKeys([], false);
};
const treeValueFind = (tree, arr, newArr = []) => {
if (typeof arr === "string") {
tree.forEach((el) => {
if (el[nodeKey] === arr) {
newArr.push(el);
}
if (el[props.defaultConf.children]) {
treeValueFind(el[props.defaultConf.children], arr, newArr);
}
});
} else {
for (let i = arr.length; i >= 0; i--) {
tree.forEach((el) => {
if (el[nodeKey] === arr[i]) {
newArr.push(el);
arr.splice(i, 1);
}
if (el[props.defaultConf.children] && arr.length > 0) {
treeValueFind(el[props.defaultConf.children], arr, newArr);
}
});
}
}
return newArr;
};
</script>
<style lang="scss" scoped>
.optionclass {
height: auto;
padding: 0;
position: relative;
width: 100%;
overflow-y: overflow;
}
.zk-frameWork-wrap {
.el-select {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-cascader
class="el-cascader-zj"
:placeholder="placeholder"
:options="tableData"
v-bind="$attrs"
v-model="modelValue"
@change="handleChange"
:props="endProps"
:filterable="filterable"
/>
<!-- <el-icon class="errorIcon"><circle-close-filled /></el-icon>
<el-icon class="checkIcon"><circle-check-filled /></el-icon> -->
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { ref, defineProps, defineEmits, defineExpose,computed } from "vue";
import { getSystemMeny } from "@/api/user-manage";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择",
type: String
},
multiple: {
default: false,
type: Boolean
},
filterable: {
default: false,
type: Boolean
},
modelValue: {
default: [],
type: Array
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const endProps = computed(() => {
let re = { children: 'sysMenuList', value: 'id', label: 'menuName' }
if (props.multiple === true) {
re.multiple = true;
}
return re;
})
const tableData = ref([]);
const getSysMenuTree = async () => {
const params = {
menuName: "",
current: 1,
size: 999
}
const res = await getSystemMeny(params);
tableData.value = res?.records;
};
getSysMenuTree();
const emits = defineEmits(["update:modelValue"]);
const handleChange = (e) => {
if (props.multiple === true) {
const data = e.map((item) => {
return item[item.length - 1];
});
emits("update:modelValue", data);
} else {
const data = e ? e[e.length - 1] : ''
emits("update:modelValue", data);
}
};
</script>
<style lang="scss" scoped>
.el-cascader-zj{
width: 100%;
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div
class="form-item-box"
:style="{ width: width}"
>
<el-input
:placeholder="placeholder"
v-bind="$attrs"
v-model="modelValue"
@input="onInput"
></el-input>
<!-- <el-icon class="errorIcon"><circle-close-filled /></el-icon>
<el-icon class="checkIcon"><circle-check-filled /></el-icon> -->
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { defineProps, defineEmits } from "vue";
const props = defineProps({
placeholder: {
default: "请输入身份证号",
type: String
},
modelValue: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const emits = defineEmits(["update:modelValue"]);
const onInput = (e) => {
emits("update:modelValue", e);
};
</script>

View File

@ -0,0 +1,71 @@
<template>
<div class="markdown-wrap">
<!---->
<div id="markdown-box">
<strong v-html="modelValue"></strong>
</div>
<div style="margin-top: 1vw">
<el-button type="primary" @click="saveEdit">确定详情信息</el-button>
</div>
</div>
</template>
<script setup>
import MKEditor from "@toast-ui/editor";
import "@toast-ui/editor/dist/toastui-editor.css";
import "@toast-ui/editor/dist/i18n/zh-cn.js";
import { COMPONENT_WIDTH } from "@/constant";
import {
ref,
defineProps,
defineEmits,
defineExpose,
onMounted,
watch
} from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请填写手机号",
type: String
},
modelValue: {
default: "",
type: String
},
width: {
default: "800px",
type: String
}
});
let mkEditor;
let el;
onMounted(() => {
el = document.querySelector("#markdown-box");
initEditor();
});
const emits = defineEmits(["update:modelValue"]);
const initEditor = () => {
mkEditor = new MKEditor({
el,
height: "700px",
previewStyle: "vertical",
language: "zh-CN",
initiaValue:props.modelValue
});
mkEditor.getMarkdown();
};
const saveEdit = () => {
emits("update:modelValue", mkEditor.getHTML());
};
const onInput = (e) => {
emits("update:modelValue", e);
};
//回显
</script>
<style lang="scss" scoped>
.markdown-wrap {
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-input :placeholder="placeholder" v-bind="$attrs" v-model="modelValue" @input="onInput" ></el-input>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from "@/constant";
import { ref, defineProps, defineEmits, defineExpose } from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请填写手机号",
type: String
},
modelValue: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const emits = defineEmits(["update:modelValue"]);
const onInput = (e) => {
emits("update:modelValue", e);
};
</script>

View File

@ -0,0 +1,32 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-input :placeholder="placeholder" v-bind="$attrs" v-model="modelValue" @input="onInput" ></el-input>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { ref, defineProps, defineEmits, defineExpose } from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请填写手机号",
type: String
},
modelValue: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const emits = defineEmits(["update:modelValue"]);
const onInput = (e) => {
emits("update:modelValue", e);
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,109 @@
<template>
<div class="form-item-box">
<el-select v-model="province" clearable placeholder="请选择省份">
<el-option v-for="item in areas" :value="item.code" :label="item.name" :key="item.code">{{ item.name }}
</el-option>
</el-select>
<el-select clearable :disabled="!province" style="margin: 0 10px" v-model="city" placeholder="请选择城市">
<el-option v-for="item in selectCity" :value="item.code" :label="item.name" :key="item.code">{{ item.name }}
</el-option>
</el-select>
<el-select clearable :disabled="!province || !city" v-model="area" placeholder="请选择区域">
<el-option v-for="item in selectArea" :value="item.code" :label="item.name" :key="item.code">{{ item.name }}
</el-option>
</el-select>
</div>
</template>
<script setup>
import allAreas from "@/constant/pca-code.json";
import {
ref,
defineProps,
defineEmits,
defineExpose,
computed,
watch
} from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择省市区",
type: String
},
modelValue: {
default: "",
type: String
}
});
let province = ref("");
let city = ref("");
let area = ref("");
let areas = ref(allAreas);
let selectCity = ref([]);
let selectArea = ref([]);
//分发事件给父组件
//分发事件给父组件
const emits = defineEmits(["update:modelValue"]);
//监听省份变化
watch(
() => province.value,
(val) => {
if (val) {
let cities = areas.value.find(
(item) => item.code === province.value
)?.children;
selectCity.value = cities;
}
city.value = "";
area.value = "";
}
);
//监听城市变化
watch(
() => city.value,
(val) => {
if (val) {
let area = selectCity.value.find(
(item) => item.code === city.value
)?.children;
selectArea.value = area;
}
area.value = "";
}
);
//监听区域变化
watch(
() => area.value,
(val) => {
let provinceData = {
code: province.value,
name:
province.value &&
allAreas.find((item) => item.code === province.value).name
};
let cityData = {
code: city.value,
name:
city.value &&
selectCity.value &&
selectCity.value.find((item) => item.code === city.value).name
};
let areaData = {
code: val,
name:
val &&
selectArea.value &&
selectArea.value.find((item) => item.code === val).name
};
emits("update:modelValue", `${provinceData},${cityData},${areaData}`);
}
);
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-cascader class="el-cascader-zj" :props="{ value: 'label', label: 'label', children: 'children' }"
:options="arercity" expand-trigger="hover" change-on-select @change="onInChange" :placeholder="placeholder"
v-bind="$attrs" v-model="value"></el-cascader>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import allAreas from "./provicesData";
import {
ref,
defineProps,
defineEmits,
defineExpose,
computed,
nextTick,
watch
} from "vue";
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择地区位置",
type: String
},
provinces2: {
default: () => [],
type: Array
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const value = ref([]);
nextTick(() => {
if (props.provinces2.length > 0) {
init(props.provinces2);
}
});
const init = (data) => { };
const registerAddress = ref([]);
const arercity = ref(allAreas);
const emits = defineEmits(["update:provinces2"]);
const onInChange = (e) => {
emits("update:provinces2", e);
};
// const onInput = (e) => {
// emits("update:provinces2", e);
// };
</script>
<style lang="scss" scoped>
.el-cascader-zj {
width: 100%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
<template>
<div>
<el-dialog
:title="titleValue"
width="1400px"
:model-value="modelValue"
@close="closed"
>
<el-form :model="listQuery" :inline="true">
<el-form-item label="所属部门">
<MOSTY.Department width="100%" clearable v-model="listQuery.ssbmdm" />
</el-form-item>
<el-form-item label="圈层名称">
<el-input
v-model="listQuery.qcmc"
placeholder="请输入圈层名称"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="success" @click="handleFilter">查询</el-button>
<el-button type="info" @click="reset()"> 重置 </el-button>
</el-form-item>
</el-form>
<div class="tabBox" style="margin-top: 0px" v-if="modelValue">
<el-table
ref="multipleUserRef"
@selection-change="handleSelectionChange"
:data="tableData"
border
style="width: 100%"
:row-key="keyid"
height="450"
>
<el-table-column
type="selection"
width="55"
:reserve-selection="true"
v-if="props.multiple"
/>
<el-table-column width="55" #default="{ row }" v-else>
<el-radio v-model="ridioIndex" :label="row.id"></el-radio>
</el-table-column>
<el-table-column
label="序号"
type="index"
align="center"
sortable
width="80"
/>
<el-table-column
sortable
prop="ssbm"
label="所属部门"
show-overflow-tooltip
align="center"
></el-table-column>
<el-table-column
sortable
prop="qcmc"
show-overflow-tooltip
label="圈层名称"
align="center"
>
</el-table-column>
<el-table-column
sortable
prop="qclx"
show-overflow-tooltip
label="圈层类型"
align="center"
>
<template #default="{ row }">
<dict-tag :options="D_BZ_QCLX" :value="row.qclx" :tag="false" />
</template>
</el-table-column>
<el-table-column
sortable
prop="qcjb"
show-overflow-tooltip
label="圈层等级"
align="center"
>
<template #default="{ row }">
<dict-tag :options="D_BZ_QCDJ" :value="row.qcjb" :tag="false" />
</template>
</el-table-column>
</el-table>
</div>
<div class="fenye" :style="{ top: tableHeight + 'px' }">
<el-pagination
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="listQuery.pageCurrent"
:page-sizes="[2, 5, 10, 20]"
:page-size="listQuery.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed">取消</el-button>
<el-button type="primary" @click="onComfirm">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import * as rule from "@/utils/rules.js";
import * as MOSTY from "@/components/MyComponents/index";
import { ElMessage } from "element-plus";
import { qcckGet, qcckPost } from "@/api/qcckApi.js";
import {
defineProps,
watch,
ref,
onMounted,
nextTick,
getCurrentInstance
} from "vue";
const { proxy } = getCurrentInstance();
const { D_BZ_QCLX, D_BZ_QCDJ } = proxy.$dict("D_BZ_QCLX", "D_BZ_QCDJ");
const props = defineProps({
//是否显示
modelValue: {
type: Boolean,
required: true
},
//标题
titleValue: {
type: String,
default: "选择圈层"
},
//是否多选
multiple: {
default: true,
type: Boolean
},
//已经选中得数据回显
data: {
type: Array,
default: []
}
});
const keyid = (row) => {
return row.id;
};
const total = ref(0);
const ridioIndex = ref(null);
const listQuery = ref({
pageCurrent: 1,
pageSize: 20,
qcmc: "",
ssbmdm: ""
});
const form = ref({});
const tableData = ref([]);
const emits = defineEmits(["close", "choosedQc"]);
const closed = () => {
emits("close", false);
};
const reset = () => {
listQuery.value = {
pageCurrent: 1,
pageSize: 20,
qcmc: "",
ssbmdm: ""
};
getListData();
};
//确认选中
const onComfirm = () => {
if (props.multiple) {
//多选
const List = JSON.parse(JSON.stringify(multipleSelectionUser.value));
if (List.length === 0) {
proxy.$message.warning("请选择圈层");
return;
}
emits("choosedQc", List);
} else {
//单选
if (![ridioIndex.value][0]) {
proxy.$message.warning("请选择圈层");
return;
}
const info = tableData.value.find((item) => {
return item.id === ridioIndex.value;
});
emits("choosedQc", [info]);
}
closed();
};
onMounted(() => {
getListData();
});
/**
* pageSize 改变触发
*/
const handleSizeChange = (currentSize) => {
listQuery.value.pageSize = currentSize;
getListData();
};
/**
* 页码改变触发
*/
const handleCurrentChange = (currentPage) => {
listQuery.value.pageSize = currentPage;
getListData();
};
//圈层数据
const getListData = async () => {
qcckGet(listQuery.value, "/mosty-jcgl/qc/selectQcList").then((res) => {
tableData.value = res?.records;
multipleUser(props.data, tableData.value);
total.value = Number(res.total);
});
};
const handleFilter = () => {
listQuery.value.pageCurrent = 1;
getListData();
};
const multipleUserRef = ref(null); //表单
//多选选中的数据
const multipleSelectionUser = ref([]);
const handleSelectionChange = (val) => {
multipleSelectionUser.value = val;
};
//回显
function multipleUser(row, list) {
if (row) {
if (props.multiple) {
row.forEach((item) => {
list.forEach((select) => {
if (item.id == select.id) {
if (multipleUserRef.value) {
multipleUserRef.value.toggleRowSelection(select, true);
}
}
});
});
}
}
}
watch(
() => props.modelValue,
(val) => {
if (val === true) {
handleFilter();
}
}
);
watch(
() => props.data,
(val) => {
if (multipleUserRef.value) multipleUser(val, tableData.value);
}
);
</script>
<style lang="scss" scoped>
@import "@/assets/css/layout.scss";
@import "@/assets/css/element-plus.scss";
::v-deep .el-form--inline {
padding-left: 0 !important;
}
::v-deep .el-radio__label {
display: none;
}
</style>
<style lang="scss" >
.el-dialog {
--el-dialog-bg-color: #001238 !important;
}
.el-dialog__title {
color: #fff !important;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<div class="Select-wrap" :style="{ width: width }">
<el-select :disabled="props.disabled" v-bind="$attrs" v-model="modelValue" @change="hanlderSelect" :popper-class="selectOption.length > 20 ? 'nation-select' : ''" :placeholder="placeholder">
<el-option v-for="item in dictEnum" :key="item.value" :label="item.zdmc || item.label" :value="item.dm || item.value">
</el-option>
</el-select>
</div>
</template>
<script setup>
import { nextTick, onBeforeMount, ref } from "vue";
const emits = defineEmits(["change"]); //子组件向父组件事件传递
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择",
type: String
},
disabled:{
default: false,
type: Boolean
},
modelValue: {
default: "",
type: String
},
dictEnum: {
default: Array,
type: String
},
width: {
default: '100%',
type: String
}
});
const selectOption = ref([]);
const hanlderSelect = (data) => {
emits("change", data);
};
</script>
<style lang="scss" scoped>
.Select-wrap {
::v-deep .el-select {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-select :disabled="disabled" v-model="sex" placeholder="请选择性别" @change="onChange">
<el-option
v-for="item in D_BZ_XB"
:key="item"
:label="item.zdmc"
:value="item.dm"
></el-option>
</el-select>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from "@/constant";
import {
ref,
defineProps,
defineEmits,
defineExpose,
getCurrentInstance,
onBeforeMount
} from "vue";
const { proxy } = getCurrentInstance();
const { D_BZ_XB } = proxy.$dict("D_BZ_XB");
const props = defineProps({
//获取组件传值
sex: {
type: String
},
disabled:{
type:Boolean,
default:false
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const emits = defineEmits(["update:sex"]);
const onChange = (e) => {
emits("update:sex", e);
};
</script>
<style lang="scss" scoped>
.manIcon,
.womanIcon {
font-size: 30px;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<div class="form-item-box zj-packageSelect-wrap" :style="{ width: width }">
<el-select v-bind="$attrs" :model-value="modelValue" @change="hanlderSelect" :popper-class="selectOption.length > 20 ? 'nation-select' : ''">
<el-option v-for="item in selectOption" :key="item.id" :label="item.postName" :value="item.id">
</el-option>
</el-select>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from '@/constant';
import { onBeforeMount, ref } from "vue";
import { selectJobPage } from "@/api/user-manage";
const emits = defineEmits(["handleChange"]); //子组件向父组件事件传递
const props = defineProps({
//获取组件传值
placeholder: {
default: "请选择",
type: String
},
modelValue: {
default: "",
type: String
},
dictEnum: {
default: "",
type: String
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const selectOption = ref([]);
onBeforeMount(async () => {
const res = await selectJobPage({ page: 1, size: 9999 });
selectOption.value = [...res.records];
});
const hanlderSelect = (data) => {
emits("handleChange", data);
};
</script>
<style lang="scss" scoped>
.zj-packageSelect-wrap {
::v-deep .el-select {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,299 @@
<template>
<div class="form-item-box" :style="{ width: width }">
<el-upload
v-bind="$attrs"
:headers="headers"
:multiple="false"
class="avatar-uploader"
:limit="props.limit"
:action="actionUrl"
list-type="picture-card"
:file-list="fileList"
show-file-list
:on-exceed="handleExceed"
:on-success="handlerSuccess"
:before-upload="beforeImgUpload"
>
<template #default>
<el-icon> <Plus /> </el-icon>
</template>
<template #file="{ file }">
<div v-if="props.isImg">
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
<el-icon>
<zoom-in />
</el-icon>
</span>
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleRemove(file, fileList)"
>
<el-icon>
<Delete />
</el-icon>
</span>
</span>
</div>
<div v-else>
<div class="file-wrap">
<span>
<svg-icon :icon="getSuffix(file.name)" />
</span>
<span class="file-name">{{ file.name }}</span>
</div>
<span class="el-upload-list__item-actions">
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleDownload(file)"
>
<el-icon>
<Download />
</el-icon>
</span>
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleRemove(file, fileList)"
>
<el-icon>
<Delete />
</el-icon>
</span>
</span>
</div>
</template>
</el-upload>
<el-dialog v-model="dialogVisible">
<img style="width: 100%" :src="dialogImageUrl" alt="" />
</el-dialog>
</div>
</template>
<script setup>
import { COMPONENT_WIDTH } from "@/constant";
import {
ref,
defineProps,
defineEmits,
defineExpose,
computed,
watch,
onMounted
} from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type from "element-plus/es/components/upload/src/upload.type";
import { useStore } from "vuex";
const props = defineProps({
//获取组件传值
modelValue: {
type: Array,
default: []
},
limit: {
type: Number,
default: 1
},
isImg: {
type: Boolean,
default: true
},
width: {
default: COMPONENT_WIDTH,
type: String
}
});
const actionUrl = computed(() =>
props.isImg
? "/mosty-api/mosty-base/minio/image/upload/id"
: "/mosty-api/mosty-base/minio/file/upload"
);
const emits = defineEmits(["update:modelValue", "handleChange"]);
//获取后缀
const getSuffix = (fileName) => {
let suffix = "";
try {
suffix = fileName.substr(fileName.lastIndexOf(".") + 1, 4); //截取最后一个点号后4个字符
} catch (err) {
suffix = "";
return "OTHER";
}
// fileName无后缀返回 false
if (!suffix) return "";
// 图片格式
var imglist = ["png", "jpg", "jpeg", "bmp", "gif"];
if (imglist.includes(suffix)) return "IMG";
//txt
if (suffix === "txt") return "TXT";
//excel XLS
const excelist = ["xls", "xlsx"];
if (excelist.includes(suffix)) return "XLS";
// 匹配 word
var wordlist = ["doc", "docx"];
if (wordlist.includes(suffix)) return "DOC";
//pdf
if (suffix === "pdf") return "PDF";
//视频 音频
var videolist = [
"mp4",
"m2v",
"mkv",
"rmvb",
"wmv",
"avi",
"flv",
"mov",
"m4v"
];
if (videolist.includes(suffix)) return "VIDEO";
var musiclist = ["mp3", "wav", "wmv"];
if (musiclist.includes(suffix)) return "MUSIC";
var pptlist = ["ppt", "pptx"];
if (pptlist.includes(suffix)) return "PPT";
//压缩包
var yslist = ["7z", "rar", "zip", "apz", "ar", "hpk", "hyp", "hbc2"];
if (yslist.includes(suffix)) return "YS";
//否则返回other
return "OTHER";
};
const imageUrl = ref("");
const store = useStore();
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const disabled = ref(false);
const headers = ref({
Authorization: store.getters.token
});
onMounted(() => {
if (props.modelValue) {
fileList.value = props.modelValue.map((el) => {
return {
url: `/mosty-api/mosty-base/minio/image/download/` + el
};
});
}
});
const fileList = ref([]);
const handlerSuccess = (res, file) => {
file.url = `/mosty-api/mosty-base/minio/image/download/` + res.data;
fileList.value.push(file);
props.modelValue.push(res.data);
emits("handleChange", props.modelValue);
// emits("update:modelValue", props.modelValue);
};
const handlePreview = (file) => {};
const handleExceed = (files, fileList) => {
ElMessage.warning(`限制,只能上传${props.limit}个文件或图片`);
};
const beforeImgUpload = (file) => {
if (props.isImg) {
let isIMG = false;
if (getSuffix(file.name) === "IMG") {
isIMG = true;
}
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isIMG) {
ElMessage.error("上传图片只能是jpg/png/jpeg/bmp/gif格式!");
}
if (!isLt5M) {
ElMessage.error("上传图片大小不能超过 5MB!");
}
return isIMG && isLt5M;
} else {
return true;
}
};
const handleAvatarSuccess = (res, file) => {
imageUrl.value = URL.createObjectURL(file.raw);
};
//查询图片
const handlePictureCardPreview = (file) => {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
};
const handleDownload = (file) => {
window.open(file.response.data);
};
const handleRemove = (file) => {
let index = fileList.value.findIndex(function (item) {
return item.url === file.url;
});
fileList.value.splice(index, 1);
props.modelValue.splice(index, 1);
emits("handleChange", props.modelValue);
// emits("update:modelValue", props.modelValue);
};
</script>
<style lang="scss" scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
.file-wrap {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
::v-deep .svg-icon {
font-size: 48px;
margin: 28px 0 2px 0;
}
.file-name {
width: 88%;
font-size: 14px;
line-height: 22px;
text-align: center;
overflow: hidden;
/*将对象作为弹性伸缩盒子模型显示*/
display: -webkit-box;
/*设置子元素排列方式*/
-webkit-box-orient: vertical;
/*设置显示的行数,多出的部分会显示为...*/
-webkit-line-clamp: 2;
}
}
</style>

View File

@ -0,0 +1,40 @@
import AddressSelect from "./AddressSelect/index.vue";
import FrameWork from "./FrameWork/index.vue";
import FrameWork2 from "./FrameWork2/index.vue";
import Phone from "./Phone/index.vue";
import IdentityCard from "./IdentityCard/index.vue";
import Email from "./Email/index.vue";
import Other from "./Other/index.vue";
import Sex from "./Sex/index.vue";
import Select from "./Select/index.vue";
import Upload from "./Upload/index.vue";
import Department from "./Department/index.vue";
import DepartmentTree from "./DepartmentTree/index.vue";
import ChooseIcon from "./ChooseIcon/index.vue";
import StationSelect from "./StationSelect/index.vue";
import Provinces from "./Provinces2/index.vue";
import MarkdownEdit from "./MarkdownEdit/index.vue";
import FileUpload from "./FileUpload/index.vue";
import RichOnly from "./RichOnly/index.vue";
import Date from "./Date/index.vue";
export {
AddressSelect,
FrameWork,
Phone,
IdentityCard,
Email,
Other,
Sex,
Select,
Upload,
FrameWork2,
Department,
DepartmentTree,
ChooseIcon,
StationSelect,
Provinces,
MarkdownEdit,
FileUpload,
RichOnly,
Date
};

View File

@ -0,0 +1,63 @@
<template >
<!---外部图标-->
<div
v-bind="$attrs"
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
:class="className"
/>
<!--内部图标-->
<svg v-else class="svg-icon" :class="className" aria-hidden="true" v-bind="$attrs">
<use :xlink:href="iconName" />
</svg>
</template>
<script setup>
import { isExternal as external } from '@/utils/validate'
import { defineProps, computed } from 'vue'
const props = defineProps({
// icon 图标
icon: {
type: String,
required: true
},
// 图标类名
className: {
type: String,
default: ''
}
})
/**
* 判断是否为外部图标
*/
const isExternal = computed(() => external(props.icon))
/**
* 外部图标样式
*/
const styleExternalIcon = computed(() => ({
mask: `url(${props.icon}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.icon}) no-repeat 50% 50%`
}))
/**
* 项目内图标
*/
const iconName = computed(() => `#icon-${props.icon}`)
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>

View File

@ -0,0 +1,198 @@
<template>
<div ref="watermarkContainerRef" class="watermark-container">
<!-- 插槽-->
<slot></slot>
</div>
</template>
<script setup>
import { ref, onMounted, watchEffect, onUnmounted, computed } from "vue";
// 使用 defineProps 定义一个组件的 props这些 props 描述了组件从父组件接收的属性
const props = defineProps({
// 文本内容,类型为字符串,必须提供,默认值为'张苹果博客'
text: {
type: Array,
required: true,
default: []
},
// 字体大小类型为数字默认值为10
fontSize: {
type: Number,
default: 15
},
// 间距类型为数字默认值为2
gap: {
type: Number,
default: 2
},
// 颜色,类型为字符串,默认值为' rgba(136, 135, 135, 0.58)'
color: {
type: String,
default: "rgba(136, 135, 135, 0.58)"
},
//角度 类型为数字 默认值 -45
angle: {
type: Number,
default: -45
}
});
/**
* 绘制多行文本
* @param ctx canvas对象
* @param words 文本数组
* @param x 坐标
* @param y 坐标(行高)
* @param maxWidth 最大宽度
*/
function drawTextWithWrap(ctx, words, x, y, maxWidth) {
let line = "";
const row = JSON.parse(JSON.stringify(y)); //拷贝一份Y的 行间距
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n];
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth) {
ctx.fillText(line, x, y);
line = words[n];
y = row * (n + 1);
} else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}
// 定义一个用于绘制水印的函数,这里可以封装一下单独引入。
// 它是一个计算属性,意味着它的值会根据其依赖的 props 的变化而自动重新计算
const waterMarkBg = (props) => {
return computed(() => {
// 创建一个新的 canvas 元素
const canvas = document.createElement("canvas");
// 获取设备的像素比如果未定义则默认为1
const devicePixelRatio = window.devicePixelRatio || 1;
// 根据像素比计算字体大小
const fontSize = props.fontSize * devicePixelRatio;
// 设置字体样式
const font = fontSize + "px serif";
// 获取 canvas 的 2D 渲染上下文
const ctx = canvas.getContext("2d");
// 设置字体
ctx.font = font;
// 测量文本的宽度
// const { width } = ctx.measureText(props.text);
// 计算 canvas 的大小,至少为 60并根据文本宽度和间距因子进行调整
const canvasSize = Math.max(60, 180) * props.gap + devicePixelRatio;
// 设置 canvas 的宽高
canvas.width = canvasSize;
canvas.height = canvasSize;
// 将 canvas 的原点移动到中心
ctx.translate(canvas.width / 5, canvas.height / 5);
// 旋转 canvas 45 度
ctx.rotate((Math.PI / 180) * props.angle);
// 设置填充颜色
ctx.fillStyle = props.color;
// 设置文本对齐方式和基线
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// 再次设置字体
ctx.font = font;
drawTextWithWrap(ctx, props.text, 0, 25, 100);
// 在 canvas 上填充文本
// ctx.fillText(props.text, 0, 0);
// 返回一个对象,包含 base64 编码的图片数据、canvas 的大小和样式尺寸
return {
base64: canvas.toDataURL(),
size: canvasSize,
styleSize: canvasSize / devicePixelRatio
};
});
};
// 用于存储 MutationObserver 的变量
let ob;
// 用于存储水印 div 的变量
let div;
// 调用 waterMarkBg 函数获取水印相关的计算属性
const bg = waterMarkBg(props);
// 创建一个 ref 用于存储水印容器的 DOM 引用
const watermarkContainerRef = ref("");
// 创建一个 ref 用于标记水印是否需要重新生成
const flag = ref(0);
// 在组件挂载后执行
onMounted(() => {
// 创建一个新的 MutationObserver用于监听水印容器的变化
ob = new MutationObserver((records) => {
// 遍历所有的变化记录
for (const record of records) {
// 遍历所有被移除的节点
for (const dom of record.removedNodes) {
// 如果被移除的节点是水印 div则更新 flag 的值并返回
if (dom === div) {
flag.value++;
return;
}
}
// 如果变化的节点就是水印 div则更新 flag 的值并返回
if (record.target === div) {
flag.value++;
return;
}
}
});
// 包括子节点的变化、属性的变化以及子树的变化
ob.observe(watermarkContainerRef.value, {
childList: true,
attributes: true,
subtree: true
});
});
//卸载
onUnmounted(() => {
ob && ob.disconnect();
div = null;
});
// 生成水印
watchEffect(() => {
// 触发 watchEffect 的重新执行
flag.value;
// 如果水印容器没有值,则直接返回,不执行后续操作
if (!watermarkContainerRef.value) {
return;
}
// 如果之前已经存在水印 div则先移除它
if (div) {
div.remove();
}
// 创建一个新的 div 元素用于作为水印的容器
div = document.createElement("div");
// 从计算属性 bg 中获取 base64 编码的图片数据和样式尺寸
const { base64, styleSize } = bg.value;
// 设置 div 的背景图片为水印图片的 base64 编码
div.style.backgroundImage = `url(${base64})`;
// 设置背景图片的尺寸
div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
// 设置背景图片重复显示
div.style.backgroundRepeat = "repeat";
// 设置水印 div 的 z-index 为 9999以确保它显示在大多数其他元素之上
div.style.zIndex = 9999;
// 设置水印 div 不响应鼠标事件,如点击、悬停等
div.style.pointerEvents = "none";
// 设置水印 div 的位置为绝对定位
div.style.position = "absolute";
// 使用 inset 属性设置 div 占据整个父容器的空间
div.style.inset = "0";
// 将水印 div 添加到水印容器中
watermarkContainerRef.value.appendChild(div);
});
</script>
<style scoped>
.watermark-container {
position: relative;
height: 100vh;
color: rgba(136, 135, 135, 0.58);
}
</style>

View File

@ -0,0 +1,214 @@
<template>
<div style="width: 100%">
<!-- hasChildren要在tableData中定义表示当前行有没有下一级 children要在tableData中定义表示下一级的数据-->
<el-table
ref="multipleTableRef"
:data="tableData"
@selection-change="handleSelectionChange"
@current-change="handleCurrentChange"
@row-click="singleElection"
:row-key="getConfiger.rowKey"
:border="getConfiger.border"
:default-expand-all="getConfiger.defaultExpandAll"
:stripe="getConfiger.stripe"
:height="tableHeight"
v-loading="tableConfiger.loading"
:lazy="getConfiger.lazy"
:load="load"
:tree-props="treePros"
style="width: 100%"
:header-cell-class-name="() => 'HeadBgColor'"
:highlight-current-row="getConfiger.showSelectType === 'radio'"
:row-style="{ height: getConfiger.rowHeight === 'auto' ? getConfiger.rowHeight : getConfiger.rowHeight + 'px'}"
>
<el-table-column
type="selection"
width="55"
v-if="getConfiger.showSelectType === 'checkBox'"
/>
<el-table-column
width="55"
v-else-if="getConfiger.showSelectType === 'radio'"
#default="{ row }"
>
<el-radio
class="radio"
v-model="getConfiger.radioChoose"
:label="row[getConfiger.rowKey]"
>&nbsp;</el-radio
>
</el-table-column>
<el-table-column
type="index"
label="序号"
v-if="getConfiger.showIndex"
width="60"
:align="getConfiger?.align"
/>
<el-table-column
v-for="(item, index) in tableColumn"
:align="getConfiger?.align"
:prop="item.prop"
:key="index"
:label="item.label"
:width="item.width"
style="width: 100%; font-size: 14px"
:show-overflow-tooltip="item.showOverflowTooltip || false"
:sortable="item.sortable || false"
>
<!-- 使用自定义 -->
<template v-if="item.showSolt" #default="scope">
<slot :name="item.prop" v-bind="scope"></slot>
</template>
<!-- 默认 -->
<template v-else #default="{ row }">
{{ row[item.prop] }}
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column
v-if="getConfiger.haveControls"
:fixed="fixed"
:label="getConfiger.controls"
:width="controlsWidth"
:align="getConfiger?.align"
>
<template #default="scope">
<slot name="controls" v-bind="scope"></slot>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { nextTick, onMounted, reactive, ref, watch, watchEffect } from "vue";
const props = defineProps({
tableConfiger: {
type: Object,
default: () => {}
},
tableData: {
type: Array,
default: () => []
},
tableColumn: {
type: Array,
default: () => {
return [];
}
},
controlsWidth: {
type: Number,
default: 180
},
tableHeight: {
type: Number
},
treePros: {
type: Object,
default: {
children: "children",
hasChildren: "hasChildren"
}
},
fixed: {
type: String,
default: "right"
}
});
// 可选的时候选择的数据
const emit = defineEmits(["chooseData"]);
const multipleTableRef = ref();
const currentRow = ref();
let getConfiger = reactive({
showSelectType: null, // 显示多选还是单选还是没有选择 checkBox/radio/null
showIndex: true, // 是否显示索引
rowKey: null, // 如果是树形表格必须设置rowKey
border: true, // 是否显示边框
defaultExpandAll: false, // 是否展开全部
loading: false,
align: "center", // 列的对齐方式 left / center / right
haveControls: true, // 是否有操作列
controls: "操作", // 操作列的表头
stripe: false, // 是否有斑马线
lazy: true, // 树形表格的懒加载必须在tableDatez中给有children的项设置 hasChildren: true,才会显示展开icon
portUrl: "", // 当树形表格使用懒加载的时候传入的请求路径/接口用于懒加载数据
defaultSelectKeys: [], // 默认选中的key集合
radioChoose: "", // 单选时选中的值------------- 待完成
rowHeight: "41" // 每行的高度
});
watchEffect(() => {
getConfiger = { ...getConfiger, ...props.tableConfiger };
setDefaultChoose();
});
onMounted(() => {
setDefaultChoose();
});
// 可选的时候选择的数据
const handleSelectionChange = (val) => {
emit("chooseData", val);
};
// 单选的时候选择的数据
const handleCurrentChange = (val) => {
currentRow.value = val;
emit("chooseData", val);
};
const singleElection = (val) => {
if (getConfiger.showSelectType === "radio") {
getConfiger.radioChoose = val[getConfiger.rowKey];
emit("chooseData", val);
}
};
// 懒加载数据的方法
const load = (date, treeNode, resolve) => {
setTimeout(() => {
resolve([
{
id: 31,
date: "2016-05-01",
name: "wangxiaohu",
address: "No. 189, Grove St, Los Angeles"
},
{
id: 32,
date: "2016-05-01",
name: "wangxiaohu",
address: "No. 189, Grove St, Los Angeles"
}
]);
}, 1000);
};
// 设置默认选中(在调用的父页面要确保 tableData传出来了如果延迟传过来默认选中不会生效
function setDefaultChoose() {
nextTick(() => {
// 多选的默认选中
if (
props.tableConfiger.defaultSelectKeys?.length > 0 &&
props.tableConfiger.showSelectType === "checkBox"
) {
props.tableData.forEach((item) => {
if (
props.tableConfiger.defaultSelectKeys.findIndex(
(v) => v === item[props.tableConfiger.rowKey]
) > -1
) {
multipleTableRef.value.toggleRowSelection(item, true);
}
});
// 单选的默认选中
} else if (
props.tableConfiger.defaultSelectKeys &&
props.tableConfiger.defaultSelectKeys?.length > 0 &&
props.tableConfiger.showSelectType === "radio"
) {
getConfiger.radioChoose = props.tableConfiger.defaultSelectKeys[0];
}
});
}
</script>
<style lang = "scss">
</style>

View File

@ -0,0 +1,71 @@
<template>
<div class="pageTitle" :style="`margin-bottom: ${marginBottom}px;background-color: ${backgroundColor}`">
<div class="title">
<ul class="flex" v-if="Array.isArray(title)">
<li :class=" idx == active ? 'hedBtn':''" @click="handleBtn(it,idx)" class="pointer ml10 mr10" v-for="(it,idx) in title" :key="it">{{ it }}</li>
</ul>
<div class="font" v-else>{{ title }}</div>
</div>
<div class="cnetr">
<slot name="center"></slot>
</div>
<div class="right">
<slot> </slot>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
defineProps({
title: {
type: String,
default: ""
},
marginBottom: {
type: Number,
default: 0
},
active: {
type: Number,
default: 0
},
backgroundColor: {
type: String,
default: "rgb(255, 255, 255, 0)"
}
});
const emit = defineEmits(["update:active","change"]);
const handleBtn = (it, idx) => {
emit("update:active", idx);
emit("change", idx);
};
</script>
<style lang = "scss" scoped>
.pageTitle {
width: 100%;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 52px;
.title {
display: flex;
margin: auto 0;
.icon {
margin: auto 0;
}
.font {
vertical-align: middle;
font-size: 18px;
}
}
.hedBtn{
color: #0072ff;
}
}
</style>

View File

@ -0,0 +1,88 @@
<!--
* @Author: 表格分页
* @Date: 2023-10-20 09:34:38
* @LastEditTime: 2023-11-13 18:34:05
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \project\src\components\aboutTable\Pages\index.vue
-->
<template>
<div class="fenye" :style="{ top: tableHeight+10 + 'px' }">
<el-pagination
:current-page="
pageData.configer.currentPage ||
pageData.configer.pageNo ||
pageData.configer.current ||
pageData.configer.pageCurrent ||
pageData.configer.pageNum"
:page-size="pageData.configer.pageSize || pageData.configer.size"
:page-sizes="pageSizeArr"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total="pageData.configer.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup>
import { reactive, ref, watchEffect } from "vue";
const props = defineProps({
pageSizeArr: {
type: Array,
default: () => [10, 20, 50, 100]
},
background: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
marginTop: {
type: Number,
default: 10
},
tableHeight: {
type: Number
},
pageConfiger: {
type: Object,
default: () => {
return {
pageSize: 10,
currentPage: 1,
total: 0
};
}
}
});
const pageData = reactive({
configer: {}
});
const emit = defineEmits(["changeSize", "changeNo"]);
// 改变每页条数
const handleSizeChange = (val) => {
emit("changeSize", val);
};
// 翻页
const handleCurrentChange = (val) => {
emit("changeNo", val);
};
watchEffect(() => {
pageData.configer = props.pageConfiger;
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,564 @@
<template>
<div
v-loading="loadingPage"
class="pageSearch searchBox"
:style="`margin-bottom: ${marginBottom}px;background-color: ${backgroundColor}`"
>
<div class="box">
<div v-for="(item, index) in getArr" :key="index" class="item">
<div class="label" v-if="item.label">{{ item.label }}</div>
<!-- select -->
<el-select
v-if="item.showType === 'select'"
v-model="searchObj[item.prop]"
:multiple="item.multiple"
:clearable="item.clearable"
:filterable="item.filterable"
:placeholder="item.placeholder"
collapse-tags
collapse-tags-tooltip
>
<el-option
v-for="obj in getOptions[item.prop]"
:key="obj.value"
:label="obj.label || obj.lable"
:value="obj.value"
/>
</el-select>
<!-- input -->
<el-input
v-else-if="item.showType === 'input'"
class="input"
v-model="searchObj[item.prop]"
:clearable="item.clearable"
:placeholder="item.placeholder"
/>
<!-- 日期段选择器 -->
<el-date-picker
v-else-if="item.showType === 'daterange'"
v-model="searchObj[item.prop]"
type="daterange"
unlink-panels
:range-separator="item.rangeSeparator"
:start-placeholder="item.startPlaceholder"
:end-placeholder="item.endPlaceholder"
:shortcuts="item.shortcuts"
:disabledDate="disabledDate"
value-format="YYYY-MM-DD"
/>
<el-date-picker
v-else-if="item.showType === 'date'"
v-model="searchObj[item.prop]"
type="date"
:placeholder="item.placeholder"
:disabled-date="disabledDate"
:shortcuts="item.shortcuts"
value-format="YYYY-MM-DD"
>
</el-date-picker>
<!-- checkbox -->
<template v-else-if="item.showType === 'department'">
<MOSTY.Department clearable v-model="item.ssbmdm" />
</template>
<!-- checkbox -->
<template v-else-if="item.showType === 'checkbox'">
<el-checkbox
v-if="item.showSelectAll"
v-model="item.checkAll"
:indeterminate="item.isIndeterminate"
@change=" (val) => { handleCheckAllChange(val, item); }"
>全选</el-checkbox>
<el-checkbox-group
v-model="searchObj[item.prop]"
@change=" (val) => { handleCheckedCitiesChange(val, item); }" >
<el-checkbox
v-for="obj in item.options"
:key="obj.value"
:label="obj.value"
>{{ obj.label }}</el-checkbox
>
</el-checkbox-group>
</template>
<!-- radio -->
<el-radio-group
v-else-if="item.showType === 'radio'"
v-model="searchObj[item.prop]"
@change="(val) => { handleRadioChange(val, item);}">
<el-radio
v-for="obj in item.options"
:key="obj.value"
:label="obj.value"
>{{ obj.label }}</el-radio
>
</el-radio-group>
<!-- 级联选择 -->
<el-cascader
v-else-if="item.showType === 'cascader'"
@change="changeca"
v-model="searchObj[item.prop]"
:props="item.props"
:show-all-levels="item.showAllLevels"
:clearable="item.clearable"
:options="getOptions[item.prop]"
:placeholder="item.placeholder"
/>
<div v-else-if="item.showType === 'defaultSlot'">
<slot name="defaultSlot"></slot>
</div>
</div>
<div class="flex">
<el-button type="primary" @click="submit">确定</el-button>
<el-button type="" @click="reset">重置</el-button>
<slot> </slot>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watch, watchEffect, nextTick, getCurrentInstance, toRefs } from "vue";
import * as MOSTY from "@/components/MyComponents/index";
const { proxy } = getCurrentInstance();
const props = defineProps({
searchArr: {
type: Array,
default: () => {
return [
{
showType: "select",
prop: "selectKey",
options: [
{
value: 1,
label: "选择1"
}
],
defaultVal: "",
label: "选择",
dict: "" // 字典编码
},
{
showType: "input",
prop: "inputKey",
defaultVal: "",
label: "输入"
},
{
showType: "daterange",
prop: "daterangeKey",
defaultVal: "",
label: "输入"
},
{
showType: "date",
prop: "date",
defaultVal: ""
},
{
showType: "checkbox",
prop: "checkboxKey1",
options: [
{
value: 1,
label: "选择1"
}
],
defaultVal: ""
},
{
showType: "cascader",
prop: "cascaderKey",
label: "级联选择",
checkStrictly: false, //点击任意选中
defaultVal: ""
},
{
showType: "radio",
defaultVal: ""
// prop: "cascaderKey",
// label: "级联选择",
// checkStrictly: false //点击任意选中
},
{
showType: "defaultTime",
prop: "timeField",
options: [],
}
];
}
},
marginBottom: {
type: Number,
default: 15
},
backgroundColor: {
type: String,
default: "rgb(255, 255, 255, 1)"
}
});
let loadingPage = ref(false);
const isShowDate = ref(false);
const emit = defineEmits(["submit", "reset"]);
let searchObj = reactive({});
const timeConfig = reactive({ //时间字典筛选和自定义日期组件相关数据
typeValue:"01", //时间字典类型默认
timeArry:[], //时间筛选自定义默认值
})
//全所或自定义选择按钮
const slectType = ref('qs');
// select 的一些默认配置
const selectDefault = {
clearable: true, // 是否可以清空
filterable: true, // 是否可以筛选
multiple: false, // 是否多选
placeholder: "请选择"
};
// 重新定义下拉框的选项
let getOptions = reactive({});
// input 的一些默认配置
const inputDefault = {
clearable: true, // 是否可以清空
placeholder: "请输入"
};
const shortcuts = [
{
text: "今天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 0);
return [start, end];
}
},
{
text: "昨天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 1);
end.setTime(end.getTime() - 3600 * 1000 * 24 * 1);
return [start, end];
}
},
{
text: "最近7天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
}
},
{
text: "最近30天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
}
},
{
text: "最近90天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
}
}
];
// daterange 的一些默认配置
const daterangeDefault = {
rangeSeparator: "至",
startPlaceholder: "开始日期",
endPlaceholder: "结束日期",
shortcuts: [], // 快捷选择
defaultShortcuts: true // 是否显示快捷选择 如果要自定义快捷选择传入一个shortcuts就可以了
};
// date 的一些默认配置
const defaultDate = {
clearable: true, // 是否可以清空
placeholder: "请输入",
shortcuts: [], // 快捷选择
defaultShortcuts: true // 是否显示快捷选择 如果要自定义快捷选择传入一个shortcuts就可以了
};
const dateShortcuts = [
{
text: "今天",
value: new Date()
},
{
text: "昨天",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
return date;
}
},
{
text: "7天前",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
return date;
}
},
{
text: "30天前",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 30);
return date;
}
},
{
text: "90天前",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 90);
return date;
}
}
];
//自定义时间选择 item 配置项 value 选中的值
const screenSelect = (item,value) => {
if(value == "08"){
searchObj[item.prop] = value;
isShowDate.value = true;
}else{
timeConfig.typeValue = value;
searchObj[item.prop] = value;
submit();
}
}
//自定义时间确定时间
const chooseDateOk = (item) => {
timeConfig.typeValue = "08";
if(timeConfig.timeArry && timeConfig.timeArry.length){ //选择了时间
searchObj[item.propStart] = timeConfig.timeArry[0];
searchObj[item.propEnd] = timeConfig.timeArry[1];
}else{ //清空了时间
searchObj[item.prop] = "01";
timeConfig.typeValue = "01";
}
isShowDate.value = false;
submit();
}
//全所-部门选择回调
const organizatioHland = (val) => {
let item = getArr.find(item=>item.showType == 'qsOrZdy');
searchObj[item.propBm] = val?.data?.orgCode || "";
if (!val || val == "") { //清空了部门选择后清空责任区ID
slectType.value = "qs";
delete searchObj[item.propZrq];
}
submit()
};
//全所-责任区回调
const zrqHland = (val) => {
let item = getArr.find(item=>item.showType == 'qsOrZdy');
searchObj[item.propZrq] = val || ""; //责任区选择
submit();
};
//自定义时间取消事件
const popoverCancel = (item) => {
isShowDate.value = false;
}
// 设置不可选的日期
const disabledDate = (time) => {
return time.getTime() > Date.now();
};
// checkbox 的一些默认配置
const defaultCheckbox = reactive({
defaultVal: [],
checkAll: false, // 全选的值
isIndeterminate: false, // 控制全选按钮样式
showSelectAll: true // 是否显示全选
});
// 全选复选框的选中与不选中
const handleCheckAllChange = (val, obj) => {
searchObj[obj.prop] = val ? obj.checkboxValueArr : [];
obj.isIndeterminate = false;
};
// 单个复选框的选中与不选中
const handleCheckedCitiesChange = (value, obj) => {
const checkedCount = value.length;
obj.checkAll = checkedCount === obj.checkboxValueArr.length;
obj.isIndeterminate =
checkedCount > 0 && checkedCount < obj.checkboxValueArr.length;
};
//单选
const handleRadioChange = (val, obj) => {
console.log(val, obj);
};
// cascader 的一些默认配置
let defaultCascader = {
filterable: true, // 是否可以搜索
clearable: true, // 是否可以清空
placeholder: "请选择",
checkStrictly: true, // 控制是否父子联动(是否可以选择任意节点)
showAllLevels: false, // 是否显示完整路径
lazy: true, // 是否懒加载 当设置为false时就要传入options
portUrl: "", // 这里必须写 接口地址
props: {
label: "label",
value: "value",
children: "children"
},
options: []
};
// 在懒加载状态下cascader 的props的一些配置
const cascaderLazyProps = reactive({
value: "value",
label: "label",
lazy: true,
lazyLoad(node, resolve) {
// 这里要根据实际情况修改
const { level } = node;
let options = [];
switch (level) {
case 0:
options = [
{
value: 1,
label: "选择1",
leaf: false // 表示有下一级 必须有这个表示 不然获取不到值
},
{
value: 2,
label: "选择2",
leaf: false // 表示有下一级
},
{
value: 3,
label: "选择3",
leaf: false // 表示有下一级
},
{
value: 4,
label: "选择4",
leaf: true // 表示没有下一级了
}
];
break;
case 1:
options = [
{
value: 11,
label: "选择1_1",
leaf: true // 表示没有下一级了
},
{
value: 21,
label: "选择2_1",
leaf: true // 表示没有下一级了
},
{
value: 31,
label: "选择3_1",
leaf: true // 表示没有下一级了
},
{
value: 41,
label: "选择4_1",
leaf: true // 表示没有下一级了
}
];
}
resolve(options);
}
});
// 级联框选择
function changeca(v) {
console.log("vvvv", v);
}
// 获取到传过来的参数
let getArr = reactive([]);
const submit = () => {
emit("submit", searchObj);
};
const reset = () => {
getArr.forEach((item) => {
searchObj[item.prop] = item.defaultVal ;
});
emit("submit", searchObj);
emit("reset", false);
};
let dataOptions = reactive([]); //时间字典筛选
watchEffect(() => {
loadingPage.value = true;
getArr = JSON.parse(JSON.stringify(props.searchArr));
getArr = getArr.map((item) => {
switch (item.showType) {
case "select":
item = { ...selectDefault, ...item };
item.options = reactive(item.options);
getOptions[item.prop] = item.options;
break;
case "input":
item = { ...inputDefault, ...item };
break;
case "daterange":
item = { ...daterangeDefault, ...item };
if (item.defaultShortcuts) {
item.shortcuts = shortcuts;
}
break;
case "date":
item = { ...defaultDate, ...item };
if (item.defaultShortcuts) {
item.shortcuts = dateShortcuts;
}
break;
case "checkbox":
item = reactive({ ...defaultCheckbox, ...item });
item.checkboxValueArr = item.options.map((obj) => {
return obj.value;
});
break;
case "cascader":
item = { ...defaultCascader, ...item };
if (item.lazy) {
cascaderLazyProps.checkStrictly = item.checkStrictly;
item.props = { ...cascaderLazyProps, ...(item.props || {}) };
delete item.options;
} else {
item.props = {
...defaultCascader.props,
...(item.props || {}),
...{ checkStrictly: item.checkStrictly }
};
getOptions[item.prop] = reactive(item.options);
}
break;
}
loadingPage.value = false;
searchObj[item.prop] = item.defaultVal ;
return item;
});
});
</script>
<style lang = "scss" scoped>
.pageSearch {
.box {
display: flex;
flex-wrap: wrap;
.item {
display: flex;
margin-right: 20px;
margin-bottom: 15px;
}
.label {
margin: auto;
margin-right: 5px;
white-space: nowrap;
color: #000;
}
}
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<div class="checkBox">
<el-checkbox class="checkall" :class="customClass" v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAll">全部</el-checkbox>
<el-checkbox-group v-model="hasChecked" @change="handleCheckedChange">
<span class="zwModel" :class="customClass"></span>
<el-checkbox :class="customClass" v-for="item in checkedList" :key="item" :label="item" :style="{width:props.width || 'auto'}">{{item}}</el-checkbox>
</el-checkbox-group>
</div>
</template>
<script setup>
import { ref, defineProps, watch, defineEmits } from "vue";
const emits = defineEmits(["changeData"]);
const props = defineProps({
data: {
type: Object,
default: {
list: [],
hasChoose: []
}
},
width:String,
customClass:String //自定义样式
});
const checkAll = ref(false);
const isIndeterminate = ref(true);
const hasChecked = ref([]); //已经全选的数据
const checkedList = ref([]);
watch(
() => props.data,
(val) => {
hasChecked.value = val.hasChoose;
checkedList.value = val.list;
handleChange(val.hasChoose); //判断是否全选
},
{
deep: true,
immediate: true
}
);
// 全选
function handleCheckAll(val) {
hasChecked.value = val ? checkedList.value : [];
isIndeterminate.value = false;
emits("changeData", hasChecked.value);
}
// 处理多选框改变
function handleCheckedChange(val) {
handleChange(val);////判断是否全选
emits("changeData", hasChecked.value);
}
//判断是否全选
function handleChange(val) {
let checkCount = val.length;
let len = checkedList.value.length;
checkAll.value = checkCount == len ? true : false;
isIndeterminate.value = checkCount > 0 && checkCount < len ? true : false;
}
</script>
<style lang="scss" scoped>
.checkBox {
display: flex;
flex-wrap:nowrap;
width:100%;
height:100%;
.checkall {
margin: 0 20px 0 4px;
}
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<transition name="el-zoom-in-center">
<div
class="el-popper is-pure is-light el-dropdown__popper ba-contextmenu"
:style="`top: ${state.axis.y + 18}px;left: ${ state.axis.x - 14 }px;width:150px`"
:key="Math.random()"
v-show="state.show"
aria-hidden="false"
data-popper-placement="bottom"
>
<ul class="el-dropdown-menu">
<template v-for="(item, idx) in props.items" :key="idx">
<li
class="el-dropdown-menu__item"
:class="item.disabled ? 'is-disabled' : ''"
tabindex="-1"
@click="onContextmenuItem(item)"
>
<Icon size="12" :name="item.icon" />
<span>{{ item.label }}</span>
</li>
</template>
</ul>
<span
class="el-popper__arrow"
:style="{ left: `${state.arrowAxis}px` }"
></span>
</div>
</transition>
</template>
<script setup>
import {
onMounted,
onUnmounted,
reactive,
toRaw,
defineProps,
defineEmits
} from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
const route = useRoute();
const props = defineProps({
items: {
type: Array,
default: []
}
});
const emits = defineEmits(["contextmenuItemClick"]);
const state = reactive({
show: false,
axis: {
x: 0,
y: 0
},
menu: {},
arrowAxis: 10
});
const onShowContextmenu = (menu, axis) => {
state.menu = menu;
state.axis = axis;
state.show = true;
};
const onContextmenuItem = (item) => {
if (item.disabled) return;
item.menu = toRaw(state.menu);
emits("contextmenuItemClick", item);
};
const onHideContextmenu = () => {
state.show = false;
};
defineExpose({
onShowContextmenu,
onHideContextmenu
});
onMounted(() => {
document.body.addEventListener("click", onHideContextmenu);
});
onUnmounted(() => {
document.body.removeEventListener("click", onHideContextmenu);
});
</script>
<style scoped lang="scss">
.ba-contextmenu {
z-index: 9999;
position: fixed;
}
.el-popper,
.el-popper.is-light .el-popper__arrow::before {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border: none;
}
.el-dropdown-menu__item {
padding: 8px 20px;
user-select: none;
}
.el-dropdown-menu__item .icon {
margin-right: 5px;
}
.el-dropdown-menu__item:not(.is-disabled) {
&:hover {
background-color: var(--el-dropdown-menuItem-hover-fill);
color: var(--el-dropdown-menuItem-hover-color);
.fa {
color: var(--el-dropdown-menuItem-hover-color) !important;
}
}
}
</style>

View File

@ -0,0 +1,179 @@
<template>
<div class="exportBox">
<el-dialog
v-model="show"
title="导入文件"
width="400px"
:show-close="true"
:center="true"
:before-close="handleClose"
>
<div class="uplodBox">
<el-upload
action="#"
drag
:on-success="handleSuccess"
:on-change="handleChange"
:show-file-list="true"
:file-list="fileDate"
accept=".xls,.xlsx"
:auto-upload="false"
>
<el-icon class="el-icon-upload" size="100"><upload-filled /></el-icon>
<div class="el-upload-text">
拖动或者点击上传或者<span @click.stop="downloadModel" class="model">下载模板</span>
</div>
<div>仅支持扩展名.xls , xlsx</div>
</el-upload>
</div>
<div class="check">
<el-checkbox true-label="true" false-label="false" v-model="isSelect"
>是否替换已存在的数据</el-checkbox
>
</div>
<div class="foot">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="onComfirm">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import axios from "axios";
import { ElMessage } from "element-plus";
import { qcckPost, qcckGet } from "@/api/qcckApi.js";
import { useStore } from "vuex";
import { ref, defineProps, defineEmits, watch } from "vue";
const store = useStore();
const props = defineProps({
show: { type: Boolean, default: false },
lx:{ type: String, default:'policeF'}
});
const headers = ref({
Authorization: "Bearer " + store.getters.token
});
const isSelect = ref(false);
const emits = defineEmits(["closeImport", "handleImport"]);
const fileDate = ref([]);
const filesList = ref({});
const baseUrl = ref('')//上传地址
const modelUrl = ref('')//下载模板地址
watch(()=>props.lx,(val)=>{
let url = ''
let moyRL = ''
switch (val) {
case 'policeF':
baseUrl.value = '/mosty-api/mosty-jcgl/tbJcglXfll/importData'
modelUrl.value = '/mosty-api/mosty-jcgl/tbJcglXfll/importTemplate'
break;
case 'car':
baseUrl.value = '/mosty-api/mosty-jcgl/tpJcglXfcl/importData'
modelUrl.value = '/mosty-api/mosty-jcgl/tpJcglXfcl/importTemplate'
break;
case 'jyqx':
baseUrl.value = '/mosty-api/mosty-jcgl/tpJcglJyqx/importData'
modelUrl.value = '/mosty-api/mosty-jcgl/tpJcglJyqx/importTemplate'
break;
case 'znzb':
baseUrl.value = '/mosty-api/mosty-jcgl/tpjcglZnzb/importData'
modelUrl.value = '/mosty-api/mosty-jcgl/tpjcglZnzb/importTemplate'
break;
default:
break;
}
},{
immediate:true
})
// 关闭
function handleClose() {
fileDate.value = [];
filesList.value = {};
emits("closeImport");
}
// 覆盖前一个文件
function handleChange(file, fileList) {
const fileSuffix = file.name.substring(file.name.lastIndexOf(".") + 1);
let whiteList = ["xls", "xlsx"];
if (!whiteList.includes(fileSuffix)) {
fileList.splice(0, 1);
filesList.value = {};
ElMessage.warning("上传只能是.xls、.xlsx 格式,请重新上传");
return false;
} else {
if (fileList.length > 1) {
fileList.splice(0, 1);
}
filesList.value = file;
}
}
// 上传成功
function handleSuccess(row) {}
// 下载模板
function downloadModel() {
window.open(modelUrl.value, "_self");
}
// 确定上传
function onComfirm() {
if (filesList.value.length <= 0) {
ElMessage.warning("请上传文件");
} else {
let file = filesList.value.raw;
let formData = new FormData();
formData.append("file", file);
formData.append("updateSupport", isSelect.value);
axios.post(baseUrl.value, formData, {"Content-type": "multipart/form-data"})
.then((res) => {
if (res.status == 200) {
let { data, message, code } = res.data;
if (code == -1) ElMessage({ type:'warning', message:message, dangerouslyUseHTMLString:true });
if(code == 10000) ElMessage({ type:'success', message:data, dangerouslyUseHTMLString:true });
emits("handleImport");
handleClose();
} else {
ElMessage.warning("文件上传失败");
}
});
}
}
</script>
<style lang="scss" scoped>
.exportBox {
.uplodBox {
width: 100%;
.el-icon {
font-size: 64px;
color: #505050;
}
.el-icon-upload {
top: 33px;
}
::v-deep .el-upload-dragger {
line-height: 50px;
}
.el-upload-text {
color: #505050;
> .model {
color: #409eff;
}
}
}
.foot {
text-align: center;
margin: 10px 0;
}
.check {
height: 20px;
}
}
</style>
<style>
.el-upload-list__item-name {
color: #fff;
}
</style>