mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-11-12 05:33:48 +08:00
Merge branch 'master' of https://gitee.com/DxrHelloWorld/smart-admin
Conflicts: smart-admin-api-java17-springboot3/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm smart-admin-web-typescript/src/api/system/employee-api.ts
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<!--
|
||||
* 目录 树形选择组件
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-12 21:01:52
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-tree-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width:${width}`"
|
||||
:dropdown-style="{ maxHeight: '400px', overflowX: 'auto' }"
|
||||
:tree-data="categoryTreeData"
|
||||
:placeholder="placeholder"
|
||||
:allowClear="true"
|
||||
tree-default-expand-all
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { categoryApi } from '/@/api/business/category/category-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const props = defineProps({
|
||||
value: Number,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
categoryType: Number,
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
// ----------------- 查询 目录 数据 -----------------
|
||||
const categoryTreeData = ref([]);
|
||||
async function queryCategoryTree() {
|
||||
if (!props.categoryType) {
|
||||
categoryTreeData.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let param = {
|
||||
categoryType: props.categoryType,
|
||||
};
|
||||
let resp = await categoryApi.queryCategoryTree(param);
|
||||
categoryTreeData.value = resp.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- 选中相关监听、事件 -----------------
|
||||
const selectValue = ref(props.value);
|
||||
// 箭头value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
// 监听类型变化
|
||||
watch(
|
||||
() => props.categoryType,
|
||||
() => {
|
||||
queryCategoryTree();
|
||||
}
|
||||
);
|
||||
|
||||
function onChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
onMounted(queryCategoryTree);
|
||||
</script>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!--
|
||||
* 公司银行 下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室:开云
|
||||
* @Date: 2022-09-02 22:12:20
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:mode="multiple ? 'multiple' : ''"
|
||||
optionFilterProp="label"
|
||||
>
|
||||
<a-select-option v-for="item in dataList" :key="item.bankId" :label="item.bankName">
|
||||
{{ item.bankName }}({{ starAccountNumber(item.accountNumber) }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { bankApi } from '/@/api/business/oa/bank-api';
|
||||
import _ from 'lodash';
|
||||
|
||||
const props = defineProps({
|
||||
value: [Number, String, Object],
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
enterpriseId: {
|
||||
type: Number,
|
||||
},
|
||||
});
|
||||
|
||||
// ------------------------ 选中 事件 ------------------------
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
const selectValue = ref(props.value);
|
||||
// 箭头value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
// 箭头货主ID变化
|
||||
watch(
|
||||
() => props.enterpriseId,
|
||||
(newValue) => {
|
||||
queryData();
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
// ------------------------ 数据查询 ------------------------
|
||||
const dataList = ref([]);
|
||||
async function queryData() {
|
||||
if (!props.enterpriseId) {
|
||||
return;
|
||||
}
|
||||
let res = await bankApi.queryList(props.enterpriseId);
|
||||
dataList.value = res.data;
|
||||
if (!props.value && !_.isEmpty(dataList.value)) {
|
||||
selectValue.value = res.data[0].invoiceId;
|
||||
handleChange(res.data[0].invoiceId);
|
||||
}
|
||||
}
|
||||
|
||||
// 银行卡号 中间位数 加星 处理
|
||||
function starAccountNumber(accountNumber) {
|
||||
if (accountNumber.length < 7) {
|
||||
return accountNumber;
|
||||
}
|
||||
return accountNumber.substr(0, 3) + '**' + accountNumber.substring(accountNumber.length - 3);
|
||||
}
|
||||
|
||||
onMounted(queryData);
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!--
|
||||
* 公司的开票信息 下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-01 23:14:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:mode="multiple ? 'multiple' : ''"
|
||||
optionFilterProp="label"
|
||||
>
|
||||
<a-select-option v-for="item in dataList" :key="item.invoiceId" :label="item.invoiceHeads">
|
||||
{{ item.invoiceHeads }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { invoiceApi } from '/@/api/business/oa/invoice-api';
|
||||
import _ from 'lodash';
|
||||
|
||||
const props = defineProps({
|
||||
value: [Number, String, Object],
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
enterpriseId: {
|
||||
type: Number,
|
||||
},
|
||||
});
|
||||
|
||||
// ------------------------ 选中 事件 ------------------------
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
|
||||
// 箭头value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
// 箭头货主ID变化
|
||||
watch(
|
||||
() => props.enterpriseId,
|
||||
(newValue) => {
|
||||
queryData();
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(value) {
|
||||
emit('update:value', value);
|
||||
emit(
|
||||
'change',
|
||||
value,
|
||||
dataList.value.find((e) => e.invoiceId == value)
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------ 数据查询 ------------------------
|
||||
|
||||
const dataList = ref([]);
|
||||
async function queryData() {
|
||||
if (!props.enterpriseId) {
|
||||
return;
|
||||
}
|
||||
let res = await invoiceApi.queryList(props.enterpriseId);
|
||||
dataList.value = res.data;
|
||||
if (!props.value && !_.isEmpty(dataList.value)) {
|
||||
selectValue.value = res.data[0].invoiceId;
|
||||
handleChange(res.data[0].invoiceId);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(queryData);
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!--
|
||||
* 企业列表 下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-26 19:16:24
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:mode="multiple ? 'multiple' : ''"
|
||||
optionFilterProp="label"
|
||||
>
|
||||
<a-select-option v-for="item in dataList" :key="item.enterpriseId" :label="item.enterpriseName">
|
||||
{{ item.enterpriseName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { enterpriseApi } from '/@/api/business/oa/enterprise-api';
|
||||
|
||||
const props = defineProps({
|
||||
value: [Number, String, Object],
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 类型 ENTERPRISE_TYPE_ENUM
|
||||
type: {
|
||||
type: Number,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
|
||||
// 箭头value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
const dataList = ref([]);
|
||||
|
||||
async function queryData() {
|
||||
let res = await enterpriseApi.queryList(props.type);
|
||||
dataList.value = res.data;
|
||||
}
|
||||
onMounted(queryData);
|
||||
</script>
|
||||
@@ -0,0 +1,87 @@
|
||||
<!--
|
||||
* 地区选择框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 15:22:45
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
|
||||
<template>
|
||||
<a-cascader
|
||||
:style="`width:${width}`"
|
||||
v-model:value="areaValue"
|
||||
:show-search="{ filter }"
|
||||
:options="areaOptionData"
|
||||
:placeholder="placeholder"
|
||||
:size="size"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { PROVINCE_CITY_DISTRICT } from './province-city-district';
|
||||
import { PROVINCE_CITY } from './province-city';
|
||||
import { ref, toRaw, watch } from 'vue';
|
||||
|
||||
// ============ 组件属性 ============
|
||||
|
||||
const TYPE_PROVINCE_CITY_DISTRICT = 'province_city_district';
|
||||
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
value: [Number, Array],
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择地区',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
// ============ 组件业务 ============
|
||||
const areaOptionData = props.type === TYPE_PROVINCE_CITY_DISTRICT ? PROVINCE_CITY_DISTRICT : PROVINCE_CITY;
|
||||
|
||||
// 绑定地区数据
|
||||
const areaValue = ref([]);
|
||||
// 监听value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
let array = [];
|
||||
for (let index = 0; index < 3; index++) {
|
||||
if (newValue[index]) {
|
||||
array.push(newValue[index].value);
|
||||
}
|
||||
}
|
||||
areaValue.value = array;
|
||||
} else {
|
||||
areaValue.value = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(value, selectedOptions) {
|
||||
emit('update:value', toRaw(selectedOptions));
|
||||
emit('change', value, toRaw(selectedOptions));
|
||||
}
|
||||
|
||||
const filter = (inputValue, path) => {
|
||||
return path.some((option) => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
|
||||
};
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
<!--
|
||||
* 布尔 树形选择组件
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-12 21:01:52
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}px`"
|
||||
:placeholder="placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="handleChange"
|
||||
>
|
||||
<a-select-option v-for="item in $smartEnumPlugin.getValueDescList('FLAG_NUMBER_ENUM')" :key="item.value" :value="item.value">
|
||||
{{ item.desc }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import _ from 'lodash';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: Number,
|
||||
width: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
function convertBoolean2number(value) {
|
||||
let result = null;
|
||||
if (_.isNaN(value) || _.isNull(value) || _.isUndefined(value)) {
|
||||
result = null;
|
||||
} else {
|
||||
result = value ? 1 : 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 箭头value变化
|
||||
const selectValue = ref(convertBoolean2number(props.value));
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = convertBoolean2number(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
const handleChange = (value) => {
|
||||
console.log('boolean enum select', value);
|
||||
let booleanResult = null;
|
||||
if (!_.isUndefined(value)) {
|
||||
booleanResult = value === 1;
|
||||
}
|
||||
emit('update:value', booleanResult);
|
||||
emit('change', booleanResult);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,133 @@
|
||||
<!--
|
||||
* 图标 选择
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-01 23:14:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<a-popover v-model:open="visible" placement="bottomLeft" trigger="click">
|
||||
<template #title>
|
||||
<a-form-item>
|
||||
<a-radio-group @change="updateSelectIconArray" v-model:value="iconStyle" style="margin: 8px">
|
||||
<a-radio-button value="outlined">线框风格</a-radio-button>
|
||||
<a-radio-button value="filled">实底风格</a-radio-button>
|
||||
<a-radio-button value="twoTone">双色风格</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input-search v-model:value="searchValue" placeholder="输入英文关键词进行搜索" @change="updateSelectIconArray" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="icon-box">
|
||||
<div v-for="item in iconLoopArray" :key="item" @click="handleClick(item)" class="icon-content">
|
||||
<component :is="$antIcons[item]" />
|
||||
</div>
|
||||
<div v-show="showMoreIndex > 0">
|
||||
<a-button type="link" @click="showMore">点击展开更多图标(因图标较多,可能会卡一小会)</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<slot name="iconSelect"></slot>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as VueIcon from '@ant-design/icons-vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
//线框风格图标数组
|
||||
const outlinedIconArray = Object.keys(VueIcon).filter((e) => _.endsWith(e.toLowerCase(), 'outlined'));
|
||||
//实底风格图标数组
|
||||
const filledIconArray = Object.keys(VueIcon).filter((e) => _.endsWith(e.toLowerCase(), 'filled'));
|
||||
//双色风格图标数组
|
||||
const twoToneIconArray = Object.keys(VueIcon).filter((e) => _.endsWith(e.toLowerCase(), 'twotone'));
|
||||
|
||||
// ------------ 显示/隐藏 ------------
|
||||
const visible = ref(false);
|
||||
|
||||
// ------------ 展开更多 ------------
|
||||
const SHOW_MORE_LENGTH = 35;
|
||||
const showMoreIndex = ref(SHOW_MORE_LENGTH);
|
||||
function showMore() {
|
||||
showMoreIndex.value = -1;
|
||||
}
|
||||
|
||||
// ------------ 图标展示与搜索 ------------
|
||||
|
||||
const iconStyle = ref('outlined');
|
||||
const selectIconArray = ref([...outlinedIconArray]);
|
||||
|
||||
const iconLoopArray = computed(() => {
|
||||
return _.slice(selectIconArray.value, 0, showMoreIndex.value);
|
||||
});
|
||||
|
||||
watch(iconStyle, (newValue, oldValue) => {
|
||||
updateSelectIconArray();
|
||||
});
|
||||
|
||||
let searchValue = ref('');
|
||||
function updateSelectIconArray() {
|
||||
let tempArray = null;
|
||||
if (iconStyle.value === 'outlined') {
|
||||
tempArray = outlinedIconArray;
|
||||
} else if (iconStyle.value === 'filled') {
|
||||
tempArray = filledIconArray;
|
||||
} else {
|
||||
tempArray = twoToneIconArray;
|
||||
}
|
||||
if (!searchValue.value) {
|
||||
selectIconArray.value = tempArray;
|
||||
} else {
|
||||
selectIconArray.value = tempArray.filter((e) => e.toLowerCase().includes(searchValue.value.toLowerCase()));
|
||||
}
|
||||
|
||||
if (selectIconArray.value.length > SHOW_MORE_LENGTH) {
|
||||
showMoreIndex.value = SHOW_MORE_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------ 对外抛出选择图标事件 ------------
|
||||
const emit = defineEmits(['updateIcon']);
|
||||
function handleClick(icon) {
|
||||
visible.value = false;
|
||||
emit('updateIcon', icon);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.icon-box {
|
||||
overflow: auto;
|
||||
font-size: 20px;
|
||||
width: 410px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.icon-content {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
.more-icon {
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
.icon-content:hover {
|
||||
background: #1890ff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
* iframe 组件
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-01 23:14:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<iframe :id="name" :src="url" frameborder="0" height="800" scrolling="yes" width="100%"></iframe>
|
||||
</template>
|
||||
<script setup>
|
||||
let props = defineProps({
|
||||
name: String,
|
||||
url: String,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<!--
|
||||
* 枚举 多选框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:32:30
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-checkbox-group :style="`width: ${width}`" v-model:value="selectValue" :options="optionList" @change="handleChange" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, getCurrentInstance, onMounted } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
enumName: String,
|
||||
value: Array,
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
});
|
||||
|
||||
// ------------ 枚举数据 加载和构建 ------------
|
||||
|
||||
const optionList = ref([]);
|
||||
function buildOptionList() {
|
||||
const internalInstance = getCurrentInstance(); // 有效 全局
|
||||
const smartEnumPlugin = internalInstance.appContext.config.globalProperties.$smartEnumPlugin;
|
||||
const valueList = smartEnumPlugin.getValueDescList(props.enumName);
|
||||
optionList.value = valueList.map((e) => Object.assign({}, { value: e.value, label: e.desc }));
|
||||
}
|
||||
|
||||
onMounted(buildOptionList);
|
||||
|
||||
// ------------ 数据选中 事件及其相关 ------------
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
function handleChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,63 @@
|
||||
<!--
|
||||
* 枚举 radio
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:32:30
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<template v-if="isButton">
|
||||
<a-radio-group v-model:value="selectValue" @change="handleChange" button-style="solid">
|
||||
<a-radio-button v-for="item in $smartEnumPlugin.getValueDescList(props.enumName)" :key="item.value" :value="item.value">
|
||||
{{ item.desc }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-radio-group v-model:value="selectValue" @change="handleChange">
|
||||
<a-radio v-for="item in $smartEnumPlugin.getValueDescList(props.enumName)" :key="item.value" :value="item.value">
|
||||
{{ item.desc }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
enumName: String,
|
||||
value: [Number, String],
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
isButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(e) {
|
||||
emit('update:value', e.target.value);
|
||||
emit('change', e.target.value);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,67 @@
|
||||
<!--
|
||||
* 枚举 下拉框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:32:30
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<a-select-option v-for="item in $smartEnumPlugin.getValueDescList(props.enumName)" :key="item.value" :value="item.value">
|
||||
{{ item.desc }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
enumName: String,
|
||||
value: [Number,String],
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* loading 组件
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-07-22 20:33:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*/
|
||||
import { useSpinStore } from "/@/store/modules/system/spin";
|
||||
|
||||
export const SmartLoading = {
|
||||
show: () => {
|
||||
useSpinStore().show();
|
||||
},
|
||||
|
||||
hide: () => {
|
||||
useSpinStore().hide();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
<!--
|
||||
* 编辑器
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 15:34:33
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div style="border: 1px solid #ccc">
|
||||
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" />
|
||||
<Editor
|
||||
style="overflow-y: hidden"
|
||||
:style="{ height: `${height}px` }"
|
||||
v-model="editorHtml"
|
||||
:defaultConfig="editorConfig"
|
||||
@onCreated="handleCreated"
|
||||
@onChange="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { shallowRef, onBeforeUnmount, watch, ref } from 'vue';
|
||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';
|
||||
import { fileApi } from '/@/api/support/file-api';
|
||||
import '@wangeditor/editor/dist/css/style.css';
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
//菜单
|
||||
const editorConfig = { MENU_CONF: {} };
|
||||
|
||||
//上传
|
||||
let customUpload = {
|
||||
async customUpload(file, insertFn) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
let res = await fileApi.uploadFile(formData, FILE_FOLDER_TYPE_ENUM.COMMON.value);
|
||||
let data = res.data;
|
||||
insertFn(data.fileUrl);
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
editorConfig.MENU_CONF['uploadImage'] = customUpload;
|
||||
editorConfig.MENU_CONF['uploadVideo'] = customUpload;
|
||||
|
||||
// ----------------------- 以下是公用变量 emits props ----------------
|
||||
const editorHtml = ref();
|
||||
let props = defineProps({
|
||||
modelValue: String,
|
||||
height: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(nVal) => {
|
||||
editorHtml.value = nVal;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 获取编辑器实例html
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const editorRef = shallowRef();
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor;
|
||||
};
|
||||
const handleChange = (editor) => {
|
||||
emit('update:modelValue', editorHtml.value);
|
||||
};
|
||||
|
||||
function getHtml() {
|
||||
const htmlContent = editorRef.value.getHtml();
|
||||
return htmlContent === '<p><br></p>' ? '' : htmlContent;
|
||||
}
|
||||
function getText() {
|
||||
return editorRef.value.getText();
|
||||
}
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
editorRef,
|
||||
getHtml,
|
||||
getText,
|
||||
});
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.w-e-full-screen-container {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<!--
|
||||
* 数据变更记录,以table形式显示
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-12 21:01:52
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-table size="small" :dataSource="tableData" :columns="columns" rowKey="dataTracerId" :pagination="false" bordered>
|
||||
<template #bodyCell="{ record, index, column }">
|
||||
<template v-if="column.dataIndex === 'dataTracerId'">
|
||||
<div>{{ index + 1 }}</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'userName'">
|
||||
<div>{{ record.userName }} ({{ $smartEnumPlugin.getDescByValue('USER_TYPE_ENUM', record.userType) }})</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'userAgent'">
|
||||
<div>{{ record.browser }} / {{ record.os }} / {{ record.device }}</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'content'">
|
||||
<div class="operate-content" v-html="record.content"></div>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'action'">
|
||||
<a-button v-if="record.diffOld || record.diffNew" @click="showDetail(record)" type="link">详情 </a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
tableData: {
|
||||
type: Array,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['showDetail']);
|
||||
function showDetail(record) {
|
||||
emit('showDetail', record);
|
||||
}
|
||||
|
||||
const columns = reactive([
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'dataTracerId',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'userName',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'IP地区',
|
||||
dataIndex: 'ipRegion',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '客户端',
|
||||
dataIndex: 'userAgent',
|
||||
ellipsis: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作内容',
|
||||
dataIndex: 'content',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 80,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.operate-content {
|
||||
font-size: 14px;
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
* 数据变更记录,以 timeline 形式显示
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-12 21:01:52
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-timeline>
|
||||
<a-timeline-item v-for="record in tableData" :key="record.dataTracerId">
|
||||
<div class="trace-div">
|
||||
<div>
|
||||
<!-- <div class="operate-content" >{{ record.content }}</div> -->
|
||||
<div class="operate-content" v-html="record.content"></div>
|
||||
<a href="javascript:void(0)" v-if="record.diffOld || record.diffNew" @click="showDetail(record)">(查看修改)</a>
|
||||
</div>
|
||||
<div class="ip-font">
|
||||
{{ record.createTime }} | {{ record.userName }} | {{ record.ipRegion }} | {{ record.ip }} | {{ record.browser }} | {{ record.os }}
|
||||
</div>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</template>
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
tableData: {
|
||||
type: Array,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['showDetail']);
|
||||
function showDetail(record) {
|
||||
emit('showDetail', record);
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.operate-content {
|
||||
font-size: 14px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.ip-font {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<!--
|
||||
* 数据变动记录 表格 组件
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-12 21:01:52
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 300px" v-model:value="queryForm.keywords" placeholder="变更内容" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="onReload">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="false">
|
||||
<!---以表格形式 显示-->
|
||||
<!-- <DataTracerTable :tableData="tableData" @showDetail="showDetail" /> -->
|
||||
|
||||
<!---以 timeline 时间轴形式 显示-->
|
||||
<DataTracerTimeline :tableData="tableData" @showDetail="showDetail" />
|
||||
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="ajaxQuery"
|
||||
@showSizeChange="ajaxQuery"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">
|
||||
<div v-html="prettyHtml"></div>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import * as Diff from 'diff';
|
||||
import * as Diff2Html from 'diff2html';
|
||||
import 'diff2html/bundles/css/diff2html.min.css';
|
||||
import uaparser from 'ua-parser-js';
|
||||
import { nextTick, reactive, ref, watch } from 'vue';
|
||||
import { dataTracerApi } from '/@/api/support/data-tracer-api';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import DataTracerTimeline from './data-tracer-timeline.vue';
|
||||
|
||||
let props = defineProps({
|
||||
// 数据id
|
||||
dataId: {
|
||||
type: Number,
|
||||
},
|
||||
// 数据 类型
|
||||
type: {
|
||||
type: Number,
|
||||
},
|
||||
});
|
||||
|
||||
// --------------- 查询表单、查询方法 ---------------
|
||||
|
||||
const queryFormState = {
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
searchCount: true,
|
||||
keywords: undefined,
|
||||
};
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
function onReload() {
|
||||
Object.assign(queryForm, queryFormState);
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
queryForm.pageNum = 1;
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
async function ajaxQuery() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await dataTracerApi.queryList(Object.assign({}, queryForm, { dataId: props.dataId, type: props.type }));
|
||||
for (const e of responseModel.data.list) {
|
||||
if (!e.userAgent) {
|
||||
continue;
|
||||
}
|
||||
// e.content = e.content.replaceAll('<br/>',';');
|
||||
let ua = uaparser(e.userAgent);
|
||||
e.browser = ua.browser.name;
|
||||
e.os = ua.os.name;
|
||||
e.device = ua.device.vendor ? ua.device.vendor + ua.device.model : '';
|
||||
}
|
||||
const list = responseModel.data.list;
|
||||
total.value = responseModel.data.total;
|
||||
tableData.value = list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========= 定义 watch 监听 ===============
|
||||
watch(
|
||||
() => props.dataId,
|
||||
(e) => {
|
||||
if (e) {
|
||||
queryForm.dataId = e;
|
||||
onSearch();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// --------------- diff 特效 ---------------
|
||||
// diff
|
||||
const visibleDiff = ref(false);
|
||||
let prettyHtml = ref('');
|
||||
function showDetail(record) {
|
||||
visibleDiff.value = true;
|
||||
let diffOld = record.diffOld.replaceAll('<br/>', '\r\n');
|
||||
let diffNew = record.diffNew.replaceAll('<br/>', '\r\n');
|
||||
const args = ['', diffOld, diffNew, '变更前', '变更后'];
|
||||
|
||||
let diffPatch = Diff.createPatch(...args);
|
||||
let html = Diff2Html.html(diffPatch, {
|
||||
drawFileList: false,
|
||||
matching: 'words',
|
||||
diffMaxChanges: 1000,
|
||||
outputFormat: 'side-by-side',
|
||||
});
|
||||
|
||||
prettyHtml.value = html;
|
||||
nextTick(() => {
|
||||
let diffDiv = document.querySelectorAll('.d2h-file-side-diff');
|
||||
if (diffDiv.length > 0) {
|
||||
let left = diffDiv[0],
|
||||
right = diffDiv[1];
|
||||
left.addEventListener('scroll', function (e) {
|
||||
if (left.scrollLeft !== right.scrollLeft) {
|
||||
right.scrollLeft = left.scrollLeft;
|
||||
}
|
||||
});
|
||||
right.addEventListener('scroll', function (e) {
|
||||
if (left.scrollLeft !== right.scrollLeft) {
|
||||
left.scrollLeft = right.scrollLeft;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,82 @@
|
||||
<!---
|
||||
* 字典key 下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室:罗伊
|
||||
* @Date: 2022-09-12 22:06:45
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="onChange"
|
||||
>
|
||||
<a-select-option v-for="item in dictKeyCodeList" :key="item.keyCode" :value="item.keyCode">
|
||||
{{ item.keyName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch, defineExpose } from 'vue';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
|
||||
defineExpose({
|
||||
queryDict,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
value: [Array, String],
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择字典',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
// 禁用标识
|
||||
disabledFlag: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
// -------------------------- 查询 字典数据 --------------------------
|
||||
|
||||
const dictKeyCodeList = ref([]);
|
||||
async function queryDict() {
|
||||
let responseModel = await dictApi.queryAllKey();
|
||||
dictKeyCodeList.value = responseModel.data;
|
||||
}
|
||||
|
||||
onMounted(queryDict);
|
||||
|
||||
// -------------------------- 选中 相关、事件 --------------------------
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
const selectValue = ref(props.value);
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function onChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!---
|
||||
* 字段 下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室:罗伊
|
||||
* @Date: 2022-09-12 22:06:45
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
:mode="mode"
|
||||
@change="onChange"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<a-select-option v-for="item in dictValueList" :key="item.valueCode" :value="item.valueCode">
|
||||
{{ item.valueName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
|
||||
const props = defineProps({
|
||||
keyCode: String,
|
||||
value: [Array, String],
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'combobox',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// -------------------------- 查询 字典数据 --------------------------
|
||||
|
||||
const dictValueList = ref([]);
|
||||
async function queryDict() {
|
||||
let res = await dictApi.valueList(props.keyCode);
|
||||
dictValueList.value = res.data;
|
||||
}
|
||||
|
||||
onMounted(queryDict);
|
||||
|
||||
// -------------------------- 选中 相关、事件 --------------------------
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
selectValue.value = value;
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
function onChange(value) {
|
||||
if (!value) {
|
||||
emit('update:value', []);
|
||||
emit('change', []);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
} else {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
<!--
|
||||
* 文件预览 弹窗
|
||||
*
|
||||
* @Author: 1024创新实验室:善逸
|
||||
* @Date: 2022-09-02 20:19:39
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-image
|
||||
class="img-prev"
|
||||
:style="{ display: 'none' }"
|
||||
:preview="{
|
||||
visible,
|
||||
onVisibleChange: setVisible,
|
||||
}"
|
||||
:src="previewUrl"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { fileApi } from '/@/api/support/file-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
|
||||
const imgFileType = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
const previewUrl = ref();
|
||||
|
||||
function showPreview(fileItem) {
|
||||
if (!fileItem.fileUrl) {
|
||||
(async () => {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let res = await fileApi.getUrl(fileItem.fileKey);
|
||||
fileItem.fileUrl = res.data;
|
||||
showFile(fileItem);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
showFile(fileItem);
|
||||
}
|
||||
}
|
||||
|
||||
const visible = ref(false);
|
||||
const setVisible = (value) => {
|
||||
visible.value = value;
|
||||
};
|
||||
|
||||
function showFile(fileItem) {
|
||||
if (isImg(fileItem.fileType)) {
|
||||
previewUrl.value = fileItem.fileUrl;
|
||||
setVisible(true);
|
||||
return;
|
||||
}
|
||||
fileApi.downLoadFile(fileItem.fileKey);
|
||||
}
|
||||
|
||||
// 判断图片类型
|
||||
function isImg(fileType) {
|
||||
return imgFileType.includes(fileType);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showPreview,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
* 文件预览
|
||||
*
|
||||
* @Author: 1024创新实验室:善逸
|
||||
* @Date: 2022-07-19 23:19:39
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="type === 'text'">
|
||||
<a v-for="(item, index) in fileList" :key="index" @click="preview(item, index)">
|
||||
{{ item.fileName }}
|
||||
<span v-if="index !== fileList.length - 1" v-html="separator"></span>
|
||||
</a>
|
||||
</template>
|
||||
<a-space>
|
||||
<a-image-preview-group :preview="{ visible, onVisibleChange: setVisible, current: previewCurrent }">
|
||||
<a-image
|
||||
v-for="(item, index) in fileList"
|
||||
:key="index"
|
||||
:src="item.fileUrl"
|
||||
:style="{ display: type === 'text' ? 'none' : '', padding: '2px', height: '100px' }"
|
||||
:width="width"
|
||||
@click="preview(item, index)"
|
||||
/>
|
||||
</a-image-preview-group>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { fileApi } from '/@/api/support/file-api';
|
||||
|
||||
let props = defineProps({
|
||||
fileList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
// 类型 text,picture
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
// image 宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
// 分隔符 可设置html标签 例如:<br/>
|
||||
separator: {
|
||||
type: String,
|
||||
default: ',',
|
||||
},
|
||||
});
|
||||
const imgFileType = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
|
||||
// 文件预览
|
||||
function preview(file, index) {
|
||||
if (imgFileType.some((e) => e === file.fileType)) {
|
||||
previewCurrent.value = index;
|
||||
visible.value = true;
|
||||
} else {
|
||||
fileApi.downLoadFile(file.fileKey);
|
||||
}
|
||||
}
|
||||
|
||||
// 预览
|
||||
const visible = ref(false);
|
||||
const previewCurrent = ref(0);
|
||||
|
||||
function setVisible(value) {
|
||||
visible.value = value;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,229 @@
|
||||
<!--
|
||||
* 文件上传
|
||||
*
|
||||
* @Author: 1024创新实验室:善逸
|
||||
* @Date: 2022-08-12 20:19:39
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div class="clearfix">
|
||||
<a-upload
|
||||
multiple
|
||||
:accept="props.accept"
|
||||
:before-upload="beforeUpload"
|
||||
:customRequest="customRequest"
|
||||
:file-list="fileList"
|
||||
:headers="{ 'x-access-token': useUserStore().getToken }"
|
||||
:list-type="listType"
|
||||
@change="handleChange"
|
||||
@preview="handlePreview"
|
||||
@remove="handleRemove"
|
||||
>
|
||||
<div v-if="fileList.length < props.maxUploadSize">
|
||||
<template v-if="listType === 'picture-card'">
|
||||
<PlusOutlined />
|
||||
<div class="ant-upload-text">
|
||||
{{ buttonText }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="listType === 'text'">
|
||||
<a-button>
|
||||
<upload-outlined />
|
||||
{{ buttonText }}
|
||||
</a-button>
|
||||
</template>
|
||||
</div>
|
||||
</a-upload>
|
||||
<a-modal :footer="null" :open="previewVisible" @cancel="handleCancel">
|
||||
<img :src="previewUrl" alt="example" style="width: 100%" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { fileApi } from '/@/api/support/file-api';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '点击上传附件',
|
||||
},
|
||||
showUploadBtn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultFileList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 最多上传文件数量
|
||||
maxUploadSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
// 上传的文件类型
|
||||
accept: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 文件上传类型
|
||||
folder: {
|
||||
type: Number,
|
||||
default: FILE_FOLDER_TYPE_ENUM.COMMON.value,
|
||||
},
|
||||
// 上传列表的内建样式,支持三种基本样式 text, picture 和 picture-card
|
||||
listType: {
|
||||
type: String,
|
||||
default: 'picture-card',
|
||||
},
|
||||
});
|
||||
|
||||
// 图片类型的后缀名
|
||||
const imgFileType = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
|
||||
// 重新修改图片展示字段
|
||||
const files = computed(() => {
|
||||
let res = [];
|
||||
if (props.defaultFileList && props.defaultFileList.length > 0) {
|
||||
props.defaultFileList.forEach((element) => {
|
||||
element.url = element.fileUrl;
|
||||
element.name = element.fileName;
|
||||
res.push(element);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
// -------------------- 逻辑 --------------------
|
||||
|
||||
const previewVisible = ref(false);
|
||||
const fileList = ref([]);
|
||||
const previewUrl = ref('');
|
||||
|
||||
watch(
|
||||
files,
|
||||
(value) => {
|
||||
fileList.value = value;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
const customRequest = async (options) => {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
console.log(options);
|
||||
const formData = new FormData();
|
||||
formData.append('file', options.file);
|
||||
let res = await fileApi.uploadFile(formData, props.folder);
|
||||
let file = res.data;
|
||||
file.url = file.fileUrl;
|
||||
file.name = file.fileName;
|
||||
fileList.value.push(file);
|
||||
emit('change', fileList.value);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function handleChange(info) {
|
||||
let fileStatus = info.file.status;
|
||||
let file = info.file;
|
||||
if (fileStatus === 'removed') {
|
||||
let index = fileList.value.findIndex((e) => e.fileId === file.fileId);
|
||||
if (index !== -1) {
|
||||
fileList.value.splice(index, 1);
|
||||
emit('change', fileList.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemove(file) {
|
||||
console.log(fileList.value);
|
||||
}
|
||||
|
||||
function beforeUpload(file, files) {
|
||||
if (fileList.value.length + files.length > props.maxUploadSize) {
|
||||
showErrorMsgOnce(`最多支持上传 ${props.maxUploadSize} 个文件哦!`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (props.accept) {
|
||||
const suffixIndex = file.name.lastIndexOf('.');
|
||||
const fileSuffix = file.name.substring(suffixIndex <= -1 ? 0 : suffixIndex);
|
||||
if (props.accept.indexOf(fileSuffix) === -1) {
|
||||
showErrorMsgOnce(`只支持上传 ${props.accept.replaceAll(',', ' ')} 格式的文件`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const isLimitSize = file.size / 1024 / 1024 < props.maxSize;
|
||||
if (!isLimitSize) {
|
||||
showErrorMsgOnce(`单个文件大小必须小于 ${props.maxSize} Mb`);
|
||||
}
|
||||
return isLimitSize;
|
||||
}
|
||||
|
||||
const showErrorModalFlag = ref(true);
|
||||
const showErrorMsgOnce = (content) => {
|
||||
if (showErrorModalFlag.value) {
|
||||
Modal.error({
|
||||
title: '提示',
|
||||
content: content,
|
||||
okType: 'danger',
|
||||
centered: true,
|
||||
onOk() {
|
||||
showErrorModalFlag.value = true;
|
||||
},
|
||||
});
|
||||
showErrorModalFlag.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
function handleCancel() {
|
||||
previewVisible.value = false;
|
||||
}
|
||||
|
||||
const handlePreview = async (file) => {
|
||||
if (imgFileType.some((e) => e === file.fileType)) {
|
||||
previewUrl.value = file.url || file.preview;
|
||||
previewVisible.value = true;
|
||||
} else {
|
||||
fileApi.downLoadFile(file.fileKey);
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------ 清空 上传 ------------------------
|
||||
function clear() {
|
||||
fileList.value = [];
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
clear,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-upload-picture-card-wrapper) {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,197 @@
|
||||
<!--
|
||||
* 表格列设置
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-26 23:45:51
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<a-tooltip title="全屏" v-if="!fullScreenFlag">
|
||||
<a-button type="text" @click="onFullScreen" size="small">
|
||||
<template #icon><fullscreen-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="取消全屏" v-if="fullScreenFlag">
|
||||
<a-button type="text" @click="onFullScreen" size="small">
|
||||
<template #icon><fullscreen-exit-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="刷新">
|
||||
<a-button type="text" @click="props.refresh" size="small">
|
||||
<template #icon><redo-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="列设置">
|
||||
<a-button type="text" @click="showModal" size="small">
|
||||
<template #icon><setting-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<SmartTableColumnModal ref="smartTableColumnModal" @change="updateColumn" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import _ from 'lodash';
|
||||
import { tableColumnApi } from '/@/api/support/table-column-api';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import SmartTableColumnModal from './smart-table-column-modal.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { mergeColumn } from './smart-table-column-merge';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config.js';
|
||||
|
||||
const props = defineProps({
|
||||
// 表格列数组
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
// 刷新表格函数
|
||||
refresh: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
// 表格id
|
||||
tableId: {
|
||||
type: Number,
|
||||
require: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
// 原始表格列数据(复制一份最原始的columns集合,以供后续各个地方使用)
|
||||
let originalColumn = _.cloneDeep(props.modelValue);
|
||||
|
||||
onMounted(() => {
|
||||
buildUserTableColumns();
|
||||
// 监听全屏事件 解决按下 ESC 退出全屏 fullScreenFlag 未改变导致下次第一下点击全屏无效的问题
|
||||
addEventListener('fullscreenchange', (event) => {
|
||||
if (document.fullscreenElement === null) {
|
||||
fullScreenFlag.value = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//构建用户的数据列
|
||||
async function buildUserTableColumns() {
|
||||
if (!props.tableId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let userTableColumnArray = [];
|
||||
try {
|
||||
let res = await tableColumnApi.getColumns(props.tableId);
|
||||
if (res.data) {
|
||||
try {
|
||||
userTableColumnArray = JSON.parse(res.data);
|
||||
} catch (e1) {
|
||||
smartSentry.captureError(e1);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
updateColumn(userTableColumnArray);
|
||||
}
|
||||
|
||||
// ----------------- 全屏 -------------------
|
||||
const fullScreenFlag = ref(false);
|
||||
|
||||
function onFullScreen() {
|
||||
if (fullScreenFlag.value) {
|
||||
// 退出全屏
|
||||
handleExitFullScreen();
|
||||
exitElementFullscreen(document.body);
|
||||
} else {
|
||||
fullScreenFlag.value = true;
|
||||
useAppConfigStore().startFullScreen();
|
||||
launchElementFullScreen(document.body);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理退出全屏
|
||||
function handleExitFullScreen() {
|
||||
fullScreenFlag.value = false;
|
||||
useAppConfigStore().exitFullScreen();
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
|
||||
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange); // Chrome, Safari and Opera
|
||||
document.removeEventListener('MSFullscreenChange', handleFullscreenChange); // Internet Explorer and Edge
|
||||
}
|
||||
|
||||
//判断各种浏览器 -全屏
|
||||
function launchElementFullScreen(element) {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen();
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen();
|
||||
} else if (element.webkitRequestFullScreen) {
|
||||
element.webkitRequestFullScreen();
|
||||
} else if (element.msRequestFullscreen) {
|
||||
element.msRequestFullscreen();
|
||||
} else {
|
||||
message.error('当前浏览器不支持部分全屏!');
|
||||
}
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
|
||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange); // Chrome, Safari and Opera
|
||||
document.addEventListener('MSFullscreenChange', handleFullscreenChange); // Internet Explorer and Edge
|
||||
}
|
||||
|
||||
function handleFullscreenChange() {
|
||||
if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
|
||||
console.log('进入全屏模式');
|
||||
} else {
|
||||
console.log('退出全屏模式');
|
||||
handleExitFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
//判断各种浏览器 -退出全屏
|
||||
function exitElementFullscreen(element) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- 弹窗 修改表格列 -------------------
|
||||
|
||||
const smartTableColumnModal = ref();
|
||||
function showModal() {
|
||||
smartTableColumnModal.value.show(originalColumn, props.tableId);
|
||||
}
|
||||
|
||||
// 将弹窗修改的列数据,赋值给原表格 列数组
|
||||
function updateColumn(changeColumnArray) {
|
||||
//合并列
|
||||
const newColumns = mergeColumn(_.cloneDeep(originalColumn), changeColumnArray);
|
||||
emit(
|
||||
'update:modelValue',
|
||||
newColumns.filter((e) => e.showFlag)
|
||||
);
|
||||
}
|
||||
|
||||
// ========= 定义 watch 监听 ===============
|
||||
watch(
|
||||
() => props.tableId,
|
||||
(e) => {
|
||||
if (e) {
|
||||
originalColumn = _.cloneDeep(props.modelValue);
|
||||
buildUserTableColumns();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 表格列设置
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-26 23:45:51
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* 将原视表格列和用户表格列进行合并、排序
|
||||
* @param {*} originalTableColumnArray
|
||||
* @param {*} userTableColumnArray
|
||||
*/
|
||||
export function mergeColumn(originalTableColumnArray, userTableColumnArray) {
|
||||
if (!userTableColumnArray) {
|
||||
return originalTableColumnArray;
|
||||
}
|
||||
|
||||
//第一步:将用户的列数据转为Map,以后备使用
|
||||
let userTableColumnMap = new Map();
|
||||
for (const item of userTableColumnArray) {
|
||||
userTableColumnMap.set(item.columnKey, item);
|
||||
}
|
||||
|
||||
//第二步:以前端的table columns列为基础,将用户后端的数据填充到前端表格列里
|
||||
let fontColumnSort = 1;
|
||||
let newColumns = [];
|
||||
for (const fontColumn of originalTableColumnArray) {
|
||||
//原始表格列默认显示
|
||||
fontColumn.columnKey = fontColumn.dataIndex;
|
||||
fontColumn.showFlag = true;
|
||||
fontColumn.sort = fontColumnSort;
|
||||
|
||||
// 如果用户存在此列,则覆盖 sort和width、showFlag字段
|
||||
let userColumn = userTableColumnMap.get(fontColumn.columnKey);
|
||||
if (userColumn) {
|
||||
fontColumn.sort = userColumn.sort;
|
||||
fontColumn.showFlag = userColumn.showFlag;
|
||||
if (userColumn.width) {
|
||||
fontColumn.width = userColumn.width;
|
||||
}
|
||||
}
|
||||
newColumns.push(fontColumn);
|
||||
fontColumnSort++;
|
||||
}
|
||||
|
||||
//第三步:前端列进行排序
|
||||
newColumns = _.sortBy(newColumns, (e) => e.sort);
|
||||
return newColumns;
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
<!--
|
||||
* 表格列设置
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-26 23:45:51
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-modal :width="800" :open="visible" title="设置列" :destroyOnClose="true" :closable="false">
|
||||
<div v-if="!tableId">
|
||||
<a-alert type="error" show-icon class="smart-margin-bottom10">
|
||||
<template #message> 您尚未设置 TableOperator 组件的 tableId</template>
|
||||
</a-alert>
|
||||
<a-alert type="error" class="smart-margin-bottom10">
|
||||
<template #message>
|
||||
1. 请在 src\constants\support\table-id-const.js 中配置 tableId 常量
|
||||
<br />
|
||||
<br />
|
||||
2. 在自己的业务表格页面组件中使用如下:
|
||||
<br />
|
||||
导入: import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
<br />
|
||||
使用: {{ '<TableOperator v-model="columns" :tableId="TABLE_ID_CONST.BUSINESS.XXX" :refresh="queryData" />' }}
|
||||
<br />
|
||||
<br />
|
||||
3. 具体用法可参考员工管理
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-alert type="info" show-icon class="smart-margin-bottom10">
|
||||
<template #icon><smile-outlined /></template>
|
||||
<template #message> 可以通过拖拽行直接修改顺序哦;( <pushpin-outlined />为固定列,不可拖拽 )</template>
|
||||
</a-alert>
|
||||
<a-table
|
||||
id="smartTableColumnModalTable"
|
||||
rowKey="columnKey"
|
||||
row-class-name="column-row"
|
||||
:columns="tableColumns"
|
||||
:dataSource="tableData"
|
||||
:rowSelection="{ checkStrictly: false, selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ text, record, index, column }">
|
||||
<template v-if="column.dataIndex === 'title'">
|
||||
<a-button type="text" :class="record.fixed ? '' : 'handle'" size="small" style="width: 100%; text-align: left">
|
||||
<template #icon>
|
||||
<drag-outlined v-if="!record.fixed" />
|
||||
<pushpin-outlined v-else />
|
||||
</template>
|
||||
{{ text }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'width'">
|
||||
<a-input-number v-model:value="record.width" style="width: 90px; margin-left: 10px; margin-right: 3px" size="small" />px
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operate'">
|
||||
<div class="smart-table-operate" v-if="!record.fixed">
|
||||
<a-button @click="up(index)" v-show="index > 0" type="link" class="handle" size="small" style="margin-right: 12px"> 上移 </a-button>
|
||||
<a-button @click="down(index)" type="link" class="handle" size="small" v-show="index !== tableData.length - 1"> 下移</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<a-button key="back" @click="hide">取消</a-button>
|
||||
<a-button v-if="tableId" key="submit" type="primary" :loading="submitLoading" @click="save">保存</a-button>
|
||||
<a-button v-if="tableId" key="back" :loading="submitLoading" @click="reset" danger style="margin-left: 20px">恢复默认</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { tableColumnApi } from '/@/api/support/table-column-api';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import Sortable from 'sortablejs';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { mergeColumn } from './smart-table-column-merge';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
defineExpose({ show });
|
||||
|
||||
// ---------------- 显示 / 隐藏 --------------------
|
||||
let tableId = null;
|
||||
const visible = ref(false);
|
||||
//显示
|
||||
function show(columns, showTableId) {
|
||||
tableId = showTableId;
|
||||
visible.value = true;
|
||||
getUserTableColumns(tableId, _.cloneDeep(columns));
|
||||
}
|
||||
|
||||
//隐藏
|
||||
function hide() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
//获取用户的列数据
|
||||
async function getUserTableColumns(tableId, columns) {
|
||||
if (!tableId) {
|
||||
return;
|
||||
}
|
||||
SmartLoading.show();
|
||||
let userTableColumnArray = [];
|
||||
try {
|
||||
let res = await tableColumnApi.getColumns(tableId);
|
||||
if (res.data) {
|
||||
try {
|
||||
userTableColumnArray = JSON.parse(res.data);
|
||||
} catch (e1) {
|
||||
smartSentry.captureError(e1);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
|
||||
//根据前端列和后端列构建新的列数据
|
||||
tableData.value = mergeColumn(columns, userTableColumnArray);
|
||||
|
||||
//将已经显示的展示出来
|
||||
for (const item of tableData.value) {
|
||||
if (item.showFlag) {
|
||||
selectedRowKeyList.value.push(item.columnKey);
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
initDrag();
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------- 表格渲染 --------------------------------
|
||||
const tableData = ref([]);
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
title: '列',
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: '宽度(像素)',
|
||||
dataIndex: 'width',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operate',
|
||||
width: 150,
|
||||
rowDrag: true,
|
||||
},
|
||||
];
|
||||
|
||||
// --------------------- 表格移动【拖拽移动、上移、下移】 --------------------------------
|
||||
//初始化拖拽
|
||||
function initDrag() {
|
||||
let tbody = document.querySelector('#smartTableColumnModalTable tbody');
|
||||
Sortable.create(tbody, {
|
||||
animation: 300,
|
||||
dragClass: 'smart-ghost-class', //设置拖拽样式类名
|
||||
ghostClass: 'smart-ghost-class', //设置拖拽停靠样式类名
|
||||
chosenClass: 'smart-ghost-class', //设置选中样式类名
|
||||
handle: '.handle',
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
if (newIndex === oldIndex) {
|
||||
return;
|
||||
}
|
||||
moveTableData(oldIndex, newIndex);
|
||||
},
|
||||
});
|
||||
}
|
||||
//上移
|
||||
function up(oldIndex) {
|
||||
let newIndex = oldIndex - 1;
|
||||
if (newIndex < 0) {
|
||||
return;
|
||||
}
|
||||
//如果下一个是固定列,则也不可移动
|
||||
if (tableData.value[newIndex].fixed) {
|
||||
return;
|
||||
}
|
||||
moveTableData(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
//下移
|
||||
function down(oldIndex) {
|
||||
let newIndex = oldIndex + 1;
|
||||
if (newIndex >= tableData.value.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
//如果下一个是固定列,则也不可移动
|
||||
if (tableData.value[newIndex].fixed) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveTableData(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
//移动表格数据
|
||||
function moveTableData(oldIndex, newIndex) {
|
||||
const currRow = tableData.value.splice(oldIndex, 1)[0];
|
||||
tableData.value.splice(newIndex, 0, currRow);
|
||||
}
|
||||
|
||||
// ----------- table 批量操作 start -----------
|
||||
const selectedRowKeyList = ref([]);
|
||||
|
||||
function onSelectChange(keyArray) {
|
||||
selectedRowKeyList.value = keyArray;
|
||||
}
|
||||
|
||||
// -------------------------提交表单【恢复默认、保存、取消】 ------------------------
|
||||
const submitLoading = ref(false);
|
||||
|
||||
//重置
|
||||
function reset() {
|
||||
Modal.confirm({
|
||||
title: '确定要恢复默认吗?',
|
||||
content: '确定恢复默认后,该信息将不可恢复',
|
||||
okText: '确定恢复',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
(async () => {
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
await tableColumnApi.deleteColumns(tableId);
|
||||
message.success('恢复默认成功');
|
||||
emit('change', []);
|
||||
hide();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
})();
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
//保存
|
||||
async function save() {
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
let columnList = [];
|
||||
for (let index = 0; index < tableData.value.length; index++) {
|
||||
let item = tableData.value[index];
|
||||
let column = {
|
||||
columnKey: item.columnKey,
|
||||
sort: index + 1,
|
||||
};
|
||||
if (item.width) {
|
||||
column.width = item.width;
|
||||
}
|
||||
column.showFlag = selectedRowKeyList.value.indexOf(item.columnKey) > -1;
|
||||
columnList.push(column);
|
||||
}
|
||||
|
||||
columnList = _.sortBy(columnList, (e) => e.sort);
|
||||
|
||||
await tableColumnApi.updateTableColumn({
|
||||
tableId,
|
||||
columnList,
|
||||
});
|
||||
|
||||
message.success('保存成功');
|
||||
emit('change', columnList);
|
||||
hide();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.column-row:hover {
|
||||
background-color: red !important;
|
||||
}
|
||||
.column-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
.blue-background-class {
|
||||
background-color: red !important;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody) {
|
||||
.ant-table-row-selected > td {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
* 部门 树形选择框
|
||||
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 23:05:43
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-tree-select
|
||||
:value="props.value"
|
||||
:treeData="treeData"
|
||||
:fieldNames="{ label: 'name', key: 'departmentId', value: 'departmentId' }"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
placeholder="请选择部门"
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:multiple="props.multiple"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { departmentApi } from '/@/api/system/department-api';
|
||||
|
||||
const props = defineProps({
|
||||
// 绑定值
|
||||
value: Number,
|
||||
// 单选多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
let treeData = ref([]);
|
||||
onMounted(queryDepartmentTree);
|
||||
|
||||
async function queryDepartmentTree() {
|
||||
let res = await departmentApi.queryDepartmentTree();
|
||||
treeData.value = res.data;
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
emit('update:value', e);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
queryDepartmentTree,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,97 @@
|
||||
<!--
|
||||
* 员工下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 15:09:02
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="onChange"
|
||||
>
|
||||
<a-select-option v-for="item in employeeList" :key="item.employeeId" :value="item.employeeId">
|
||||
{{ item.actualName }}
|
||||
<template v-if="item.departmentName"> ({{ item.departmentName }}) </template>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
// =========== 属性定义 和 事件方法暴露 =============
|
||||
|
||||
const props = defineProps({
|
||||
value: [Number, Array],
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
// 角色ID,可为空
|
||||
roleId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
// 禁用标识
|
||||
disabledFlag: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
// =========== 查询数据 =============
|
||||
|
||||
//员工列表数据
|
||||
const employeeList = ref([]);
|
||||
async function query() {
|
||||
try {
|
||||
let params = {};
|
||||
if (props.roleId) {
|
||||
params = { roleId: props.roleId };
|
||||
}
|
||||
if (null != props.disabledFlag) {
|
||||
params.disabledFlag = props.disabledFlag;
|
||||
}
|
||||
let resp = await employeeApi.queryAll(params);
|
||||
employeeList.value = resp.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
onMounted(query);
|
||||
|
||||
// =========== 选择 监听、事件 =============
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function onChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<!--
|
||||
* 员工 表格 弹窗 选择框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-19 23:09:02
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-modal v-model:open="visible" :width="900" title="选择人员" @cancel="closeModal" @ok="onSelectEmployee">
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 150px" v-model:value="params.keyword" placeholder="关键字" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门" class="smart-query-form-item">
|
||||
<DepartmentTreeSelect style="width: 200px" ref="departmentTreeSelect" v-model:value="params.departmentId" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" class="smart-query-form-item">
|
||||
<a-select style="width: 120px" v-model:value="params.disabledFlag" placeholder="请选择状态" allowClear>
|
||||
<a-select-option :key="1"> 禁用 </a-select-option>
|
||||
<a-select-option :key="0"> 启用 </a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="reset" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<a-table
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange, getCheckboxProps: getCheckboxProps }"
|
||||
:loading="tableLoading"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="false"
|
||||
bordered
|
||||
rowKey="employeeId"
|
||||
:scroll="{ y: 300 }"
|
||||
>
|
||||
<template #bodyCell="{ text, column }">
|
||||
<template v-if="column.dataIndex === 'disabledFlag'">
|
||||
<a-tag :color="text ? 'error' : 'processing'">{{ text ? '禁用' : '启用' }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.dataIndex === 'gender'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('GENDER_ENUM', text) }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="params.pageSize"
|
||||
v-model:current="params.pageNum"
|
||||
v-model:pageSize="params.pageSize"
|
||||
:total="total"
|
||||
@change="queryEmployee"
|
||||
@showSizeChange="queryEmployee"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
const emits = defineEmits(['selectData']);
|
||||
defineExpose({
|
||||
showModal,
|
||||
});
|
||||
|
||||
// ----------------------- modal 显示与隐藏 ---------------------
|
||||
|
||||
const visible = ref(false);
|
||||
async function showModal(selectEmployeeId) {
|
||||
originalRowKeyList.value = selectEmployeeId || [];
|
||||
selectedRowKeyList.value = selectEmployeeId || [];
|
||||
visible.value = true;
|
||||
onSearch();
|
||||
}
|
||||
function closeModal() {
|
||||
Object.assign(params, defaultParams);
|
||||
selectedRowKeyList.value = [];
|
||||
visible.value = false;
|
||||
}
|
||||
// ----------------------- 员工查询表单与查询 ---------------------
|
||||
const tableLoading = ref(false);
|
||||
const departmentTreeSelect = ref();
|
||||
const total = ref();
|
||||
|
||||
let defaultParams = {
|
||||
departmentId: undefined,
|
||||
disabledFlag: undefined,
|
||||
employeeIdList: undefined,
|
||||
keyword: undefined,
|
||||
searchCount: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
sortItemList: undefined,
|
||||
};
|
||||
const params = reactive({ ...defaultParams });
|
||||
function reset() {
|
||||
Object.assign(params, defaultParams);
|
||||
queryEmployee();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
params.pageNum = 1;
|
||||
queryEmployee();
|
||||
}
|
||||
|
||||
async function queryEmployee() {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
let res = await employeeApi.queryEmployee(params);
|
||||
tableData.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 员工表格选择 ---------------------
|
||||
const originalRowKeyList = ref([]);
|
||||
let selectedRowKeyList = ref([]);
|
||||
const hasSelected = computed(() => selectedRowKeyList.value.length !== originalRowKeyList.value.length);
|
||||
|
||||
function onSelectChange(selectedRowKeys) {
|
||||
selectedRowKeyList.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
function onSelectEmployee() {
|
||||
if (!hasSelected.value) {
|
||||
message.warning('请选择角色人员');
|
||||
return;
|
||||
}
|
||||
// 过滤出新选择的人员id
|
||||
const newEmployeeIdList = selectedRowKeyList.value.filter((id) => !originalRowKeyList.value.includes(id));
|
||||
emits('selectData', newEmployeeIdList);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function getCheckboxProps(record) {
|
||||
return {
|
||||
// 角色员工列表的添加员工弹窗中 禁止添加选择已存在该角色的员工
|
||||
disabled: originalRowKeyList.value.includes(record.employeeId),
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------- 员工表格渲染 ---------------------
|
||||
const tableData = ref([]);
|
||||
//字段
|
||||
const columns = [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'actualName',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'loginName',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'disabledFlag',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<!--
|
||||
* 菜单 树形 下拉选择框
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-01 23:14:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-tree-select
|
||||
:value="props.value"
|
||||
:treeData="treeData"
|
||||
:fieldNames="{ label: 'menuName', key: 'menuId', value: 'menuId' }"
|
||||
show-search
|
||||
tree-checkable
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
placeholder="请选择菜单"
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
@change="onSelectChange"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { menuApi } from '/@/api/system/menu-api';
|
||||
import { buildMenuTableTree } from '/@/views/system/menu/menu-data-handler';
|
||||
import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
|
||||
|
||||
const props = defineProps({
|
||||
// 绑定值
|
||||
value: Array,
|
||||
// 单选多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
let treeData = ref([]);
|
||||
onMounted(queryMenuTree);
|
||||
|
||||
// 外部调用初始化
|
||||
let menuList = [];
|
||||
async function queryMenuTree() {
|
||||
let res = await menuApi.queryMenu();
|
||||
menuList = res.data.filter((e) => e.menuType === MENU_TYPE_ENUM.MENU.value || e.menuType === MENU_TYPE_ENUM.CATALOG.value);
|
||||
for (const item of menuList) {
|
||||
if (item.menuType === MENU_TYPE_ENUM.CATALOG.value) {
|
||||
item.disabled = true;
|
||||
}
|
||||
}
|
||||
treeData.value = buildMenuTableTree(menuList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id集合,获取菜单集合
|
||||
*/
|
||||
function getMenuListByIdList(menuIdList) {
|
||||
return _.cloneDeep(menuList.filter((e) => menuIdList.indexOf(e.menuId) > -1));
|
||||
}
|
||||
|
||||
function onSelectChange(e) {
|
||||
emit('update:value', e);
|
||||
}
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ------------------------
|
||||
defineExpose({
|
||||
queryMenuTree,
|
||||
getMenuListByIdList,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
* 职位
|
||||
*
|
||||
* @Author: 开云
|
||||
* @Date: 2024-06-27 23:09:02
|
||||
* @Wechat: kaiyun
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="onChange"
|
||||
>
|
||||
<a-select-option v-for="item in positionList" :key="item.positionId" :value="item.positionId">
|
||||
{{ item.positionName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { positionApi } from '/@/api/system/position-api.js';
|
||||
|
||||
// =========== 属性定义 和 事件方法暴露 =============
|
||||
|
||||
const props = defineProps({
|
||||
value: [Number, Array],
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
// =========== 查询数据 =============
|
||||
|
||||
//员工列表数据
|
||||
const positionList = ref([]);
|
||||
async function query() {
|
||||
try {
|
||||
let resp = await positionApi.queryList();
|
||||
positionList.value = resp.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
onMounted(query);
|
||||
|
||||
// =========== 选择 监听、事件 =============
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function onChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user