feat:人员打卡配置
This commit is contained in:
1 parent
cdb7496ade
commit
866a11277e
21 files changed
+1357
-269
No files matched your search
+1
-1
@@ -120,4 +120,4 @@
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/log4j/pig"
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
import request from "/@/utils/request";
|
||||
|
||||
export function change(data?: { userCodeList: string[]; enable: "1" | "0" }) {
|
||||
return request({
|
||||
url: "/admin/sysPersonClockConfig/change",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -109,7 +109,7 @@ const props = withDefaults(
|
||||
nodeClickable: true,
|
||||
switchInactiveText: "当前级",
|
||||
switchActiveText: "当前级及子级",
|
||||
defaultTreeWidth: 20,
|
||||
defaultTreeWidth: 15,
|
||||
},
|
||||
)
|
||||
const emit = defineEmits(["getSelectNode"])
|
||||
|
||||
@@ -112,7 +112,7 @@ const props = withDefaults(
|
||||
nodeClickable: true,
|
||||
switchInactiveText: "当前级",
|
||||
switchActiveText: "当前级及子级",
|
||||
defaultTreeWidth: 20,
|
||||
defaultTreeWidth: 15,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ const props = withDefaults(
|
||||
nodeClickable: true,
|
||||
switchInactiveText: "当前级",
|
||||
switchActiveText: "当前级及子级",
|
||||
defaultTreeWidth: 20,
|
||||
defaultTreeWidth: 15,
|
||||
windowNum: null
|
||||
},
|
||||
)
|
||||
|
||||
@@ -109,7 +109,7 @@ const props = withDefaults(
|
||||
nodeClickable: true,
|
||||
switchInactiveText: "当前级",
|
||||
switchActiveText: "当前级及子级",
|
||||
defaultTreeWidth: 20,
|
||||
defaultTreeWidth: 15,
|
||||
},
|
||||
)
|
||||
const emit = defineEmits(["getSelectNode"])
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
label: 'treeLabel',
|
||||
children: 'deviceChannelList',
|
||||
}" node-key="treeValue" v-model="state.selectId" :placeholder="props.placeholder" :clearable="props.clearable"
|
||||
:loading="state.loading" :data="state.options" :render-after-expand="false" show-checkbox multiple
|
||||
style="width: 100%" filterable :filter-node-method="filterNode" default-expand-all
|
||||
@visible-change="(e: boolean) => e && getOptions()">
|
||||
:loading="state.loading" :data="state.options" :render-after-expand="true" show-checkbox multiple
|
||||
style="width: 100%" filterable :filter-node-method="filterNode"
|
||||
@visible-change="(e: boolean) => e && getOptions(true)">
|
||||
<template #default="{ data, node }">
|
||||
<div v-if="node.level == 1" class="flex items-center gap-2">
|
||||
<el-tag size="small" effect="dark" color="var(--el-color-primary)">硬盘录像机</el-tag>
|
||||
@@ -111,8 +111,29 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const getCacheKey = () => `SelectTreeChannel_Cache_${JSON.stringify(props.apiParams || {})}`
|
||||
|
||||
async function ensureOptionsLoaded(force = false) {
|
||||
if (state.loaded && !force) return
|
||||
|
||||
if (!force) {
|
||||
try {
|
||||
const cacheKey = getCacheKey()
|
||||
const cached = localStorage.getItem(cacheKey)
|
||||
if (cached) {
|
||||
state.options = JSON.parse(cached)
|
||||
state.loaded = true
|
||||
if (props.autoFirst && state.options.length != 0) {
|
||||
state.selectId = state.options[0].id
|
||||
emit("change", state.selectId)
|
||||
}
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse cached tree data', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (loadOptionsTask) return loadOptionsTask
|
||||
loadOptionsTask = getOptions(force).finally(() => {
|
||||
loadOptionsTask = null
|
||||
@@ -131,6 +152,14 @@ async function getOptions(force = false) {
|
||||
const { data } = await DeviceManageApi.channelTree(_pa)
|
||||
state.options = data ?? []
|
||||
state.loaded = true
|
||||
|
||||
try {
|
||||
const cacheKey = getCacheKey()
|
||||
localStorage.setItem(cacheKey, JSON.stringify(state.options))
|
||||
} catch (e) {
|
||||
console.error('Failed to cache tree data', e)
|
||||
}
|
||||
|
||||
if (props.autoFirst && state.options.length != 0) {
|
||||
state.selectId = state.options[0].id
|
||||
emit("change", state.selectId)
|
||||
|
||||
+218
-216
@@ -1,251 +1,253 @@
|
||||
import { CellStyle, ElMessage } from 'element-plus';
|
||||
import other from '/@/utils/other';
|
||||
import { CellStyle, ElMessage } from "element-plus";
|
||||
import other from "/@/utils/other";
|
||||
|
||||
/**
|
||||
* 表格组件基础配置属性
|
||||
*/
|
||||
export interface BasicTableProps {
|
||||
// 是否在创建页面时即调用数据列表接口,默认为true
|
||||
createdIsNeed?: boolean;
|
||||
// 是否需要分页,默认为true
|
||||
isPage?: boolean;
|
||||
// 查询条件表单对象,类型为any
|
||||
queryForm?: any;
|
||||
// 数据列表数组
|
||||
dataList?: any[];
|
||||
// 分页属性对象
|
||||
pagination?: Pagination;
|
||||
// 数据列表,loading状态标志,默认为false
|
||||
dataListLoading?: boolean;
|
||||
// 数据列表多选项数组
|
||||
dataListSelections?: any[];
|
||||
// 数据列表查询接口api方法,接收任意数量参数,返回Promise
|
||||
pageList?: (...arg: any) => Promise<any>;
|
||||
// loading标志,默认为false
|
||||
loading?: Boolean;
|
||||
// 多选结果数组
|
||||
selectObjs?: any[];
|
||||
// 排序字段数组
|
||||
descs?: string[];
|
||||
// 排序方式数组
|
||||
ascs?: string[];
|
||||
// props属性对象,类型为any
|
||||
props?: any;
|
||||
// 是否在创建页面时即调用数据列表接口,默认为true
|
||||
createdIsNeed?: boolean;
|
||||
// 是否需要分页,默认为true
|
||||
isPage?: boolean;
|
||||
// 查询条件表单对象,类型为any
|
||||
queryForm?: any;
|
||||
// 数据列表数组
|
||||
dataList?: any[];
|
||||
// 分页属性对象
|
||||
pagination?: Pagination;
|
||||
// 数据列表,loading状态标志,默认为false
|
||||
dataListLoading?: boolean;
|
||||
// 数据列表多选项数组
|
||||
dataListSelections?: any[];
|
||||
// 数据列表查询接口api方法,接收任意数量参数,返回Promise
|
||||
pageList?: (...arg: any) => Promise<any>;
|
||||
// loading标志,默认为false
|
||||
loading?: Boolean;
|
||||
// 多选结果数组
|
||||
selectObjs?: any[];
|
||||
// 排序字段数组
|
||||
descs?: string[];
|
||||
// 排序方式数组
|
||||
ascs?: string[];
|
||||
// props属性对象,类型为any
|
||||
props?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格样式。
|
||||
*/
|
||||
export interface TableStyle {
|
||||
cellStyle: CellStyle<any>;
|
||||
headerCellStyle: CellStyle<any>;
|
||||
cellStyle: CellStyle<any>;
|
||||
headerCellStyle: CellStyle<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页属性配置接口
|
||||
*/
|
||||
export interface Pagination {
|
||||
// 当前页码,默认为1
|
||||
current?: number;
|
||||
// 每页显示条数,默认为10
|
||||
size?: number;
|
||||
// 总条数,默认为0
|
||||
total?: number;
|
||||
// 每页显示条数选择器的选项数组,默认为[10,20,30,40]
|
||||
pageSizes?: any[];
|
||||
// 分页组件布局方式,可选值有 total,sizes,prev,jump,next,默认为'total,sizes,prev,jump,next'
|
||||
layout?: String;
|
||||
// 当前页码,默认为1
|
||||
current?: number;
|
||||
// 每页显示条数,默认为10
|
||||
size?: number;
|
||||
// 总条数,默认为0
|
||||
total?: number;
|
||||
// 每页显示条数选择器的选项数组,默认为[10,20,30,40]
|
||||
pageSizes?: any[];
|
||||
// 分页组件布局方式,可选值有 total,sizes,prev,jump,next,默认为'total,sizes,prev,jump,next'
|
||||
layout?: String;
|
||||
}
|
||||
|
||||
export function useTable(options?: BasicTableProps) {
|
||||
const defaultOptions: BasicTableProps = {
|
||||
// 列表数据是否正在加载中,默认为false
|
||||
dataListLoading: false,
|
||||
// 是否需要自动请求创建接口来获取表格数据,默认为true
|
||||
createdIsNeed: true,
|
||||
// 是否展示分页组件,默认为true
|
||||
isPage: true,
|
||||
// 查询表单对象,用于提交条件查询时的参数传递,默认为空对象
|
||||
queryForm: {},
|
||||
// 表格展示的数据数组,默认为空数组
|
||||
dataList: [],
|
||||
// 分页组件属性配置,如当前页码、每页展示数据条数等,默认值为 {current:1, size:10,total:0,pageSizes:[1, 10, 20, 50, 100, 200],layout:'total, sizes, prev, pager, next, jumper'}
|
||||
pagination: {
|
||||
current: 1,
|
||||
size: 20,
|
||||
total: 0,
|
||||
pageSizes: [1, 10, 20, 50, 100, 200],
|
||||
layout: 'total, sizes, prev, pager, next, jumper',
|
||||
} as Pagination,
|
||||
// 当前选中的数据项,默认为空数组
|
||||
dataListSelections: [],
|
||||
// 是否正在从服务器加载数据,默认为false
|
||||
loading: false,
|
||||
// 表格数据项的选择数据,默认为空数组
|
||||
selectObjs: [],
|
||||
// 排序时使用的字段名数组,如 ['id','name'],默认为空数组
|
||||
descs: [],
|
||||
// 排序方向数组,如 ['asc', 'desc'],默认为空数组
|
||||
ascs: [],
|
||||
// props属性配置对象,用于自定义数据属性,默认值为 {item:'records',totalCount:'total'}
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total',
|
||||
},
|
||||
};
|
||||
const defaultOptions: BasicTableProps = {
|
||||
// 列表数据是否正在加载中,默认为false
|
||||
dataListLoading: false,
|
||||
// 是否需要自动请求创建接口来获取表格数据,默认为true
|
||||
createdIsNeed: true,
|
||||
// 是否展示分页组件,默认为true
|
||||
isPage: true,
|
||||
// 查询表单对象,用于提交条件查询时的参数传递,默认为空对象
|
||||
queryForm: {},
|
||||
// 表格展示的数据数组,默认为空数组
|
||||
dataList: [],
|
||||
// 分页组件属性配置,如当前页码、每页展示数据条数等,默认值为 {current:1, size:10,total:0,pageSizes:[1, 10, 20, 50, 100, 200],layout:'total, sizes, prev, pager, next, jumper'}
|
||||
pagination: {
|
||||
current: 1,
|
||||
size: 20,
|
||||
total: 0,
|
||||
pageSizes: [1, 10, 20, 50, 100],
|
||||
layout: "total, sizes, prev, pager, next, jumper",
|
||||
} as Pagination,
|
||||
// 当前选中的数据项,默认为空数组
|
||||
dataListSelections: [],
|
||||
// 是否正在从服务器加载数据,默认为false
|
||||
loading: false,
|
||||
// 表格数据项的选择数据,默认为空数组
|
||||
selectObjs: [],
|
||||
// 排序时使用的字段名数组,如 ['id','name'],默认为空数组
|
||||
descs: [],
|
||||
// 排序方向数组,如 ['asc', 'desc'],默认为空数组
|
||||
ascs: [],
|
||||
// props属性配置对象,用于自定义数据属性,默认值为 {item:'records',totalCount:'total'}
|
||||
props: {
|
||||
item: "records",
|
||||
totalCount: "total",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 合并默认属性配置和自定义属性配置
|
||||
* @param options 默认属性配置对象
|
||||
* @param props 自定义属性配置对象
|
||||
* @returns 合并后的属性配置对象
|
||||
*/
|
||||
const mergeDefaultOptions = (options: any, props: any): BasicTableProps => {
|
||||
for (const key in options) {
|
||||
if (!Object.getOwnPropertyDescriptor(props, key)) {
|
||||
props[key] = options[key];
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
/**
|
||||
* 合并默认属性配置和自定义属性配置
|
||||
* @param options 默认属性配置对象
|
||||
* @param props 自定义属性配置对象
|
||||
* @returns 合并后的属性配置对象
|
||||
*/
|
||||
const mergeDefaultOptions = (options: any, props: any): BasicTableProps => {
|
||||
for (const key in options) {
|
||||
if (!Object.getOwnPropertyDescriptor(props, key)) {
|
||||
props[key] = options[key];
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
|
||||
// 覆盖默认值
|
||||
const state = mergeDefaultOptions(defaultOptions, options);
|
||||
// 覆盖默认值
|
||||
const state = mergeDefaultOptions(defaultOptions, options);
|
||||
|
||||
/**
|
||||
* 发起分页查询,并设置表格数据和分页信息
|
||||
*/
|
||||
const query = async () => {
|
||||
// 判断是否存在state.pageList属性
|
||||
if (state.pageList) {
|
||||
try {
|
||||
// 开始加载数据,设置state.loading为true
|
||||
state.loading = true;
|
||||
/**
|
||||
* 发起分页查询,并设置表格数据和分页信息
|
||||
*/
|
||||
const query = async () => {
|
||||
// 判断是否存在state.pageList属性
|
||||
if (state.pageList) {
|
||||
try {
|
||||
// 开始加载数据,设置state.loading为true
|
||||
state.loading = true;
|
||||
|
||||
// 调用state.pageList方法发起分页查询
|
||||
const res = await state.pageList({
|
||||
...state.queryForm,
|
||||
current: state.pagination?.current,
|
||||
size: state.pagination?.size,
|
||||
descs: state.descs?.join(','),
|
||||
ascs: state.ascs?.join(','),
|
||||
});
|
||||
// 调用state.pageList方法发起分页查询
|
||||
const res = await state.pageList({
|
||||
...state.queryForm,
|
||||
current: state.pagination?.current,
|
||||
size: state.pagination?.size,
|
||||
descs: state.descs?.join(","),
|
||||
ascs: state.ascs?.join(","),
|
||||
});
|
||||
|
||||
// 设置表格展示的数据数组
|
||||
state.dataList = state.isPage ? res.data[state.props.item] : res.data;
|
||||
// 设置分页信息中的总数据条数
|
||||
state.pagination!.total = state.isPage ? res.data[state.props.totalCount] : 0;
|
||||
} catch (err: any) {
|
||||
// 捕获异常并显示错误提示
|
||||
ElMessage.error(err.msg || err.data.msg);
|
||||
} finally {
|
||||
// 结束加载数据,设置state.loading为false
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 设置表格展示的数据数组
|
||||
state.dataList = state.isPage ? res.data[state.props.item] : res.data;
|
||||
// 设置分页信息中的总数据条数
|
||||
state.pagination!.total = state.isPage
|
||||
? res.data[state.props.totalCount]
|
||||
: 0;
|
||||
} catch (err: any) {
|
||||
// 捕获异常并显示错误提示
|
||||
ElMessage.error(err.msg || err.data.msg);
|
||||
} finally {
|
||||
// 结束加载数据,设置state.loading为false
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (state.createdIsNeed) {
|
||||
query();
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
if (state.createdIsNeed) {
|
||||
query();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 分页大小改变事件处理函数
|
||||
* @param val 新的分页大小
|
||||
*/
|
||||
const sizeChangeHandle = (val: number) => {
|
||||
// 修改state.pagination中的size属性
|
||||
state.pagination!.size = val;
|
||||
// 再次发起查询操作
|
||||
query();
|
||||
};
|
||||
/**
|
||||
* 分页大小改变事件处理函数
|
||||
* @param val 新的分页大小
|
||||
*/
|
||||
const sizeChangeHandle = (val: number) => {
|
||||
// 修改state.pagination中的size属性
|
||||
state.pagination!.size = val;
|
||||
// 再次发起查询操作
|
||||
query();
|
||||
};
|
||||
|
||||
/**
|
||||
* 当前页码改变事件处理函数
|
||||
* @param val 新的页码
|
||||
*/
|
||||
const currentChangeHandle = (val: number) => {
|
||||
// 修改state.pagination中的current属性
|
||||
state.pagination!.current = val;
|
||||
// 再次发起查询操作
|
||||
query();
|
||||
};
|
||||
/**
|
||||
* 当前页码改变事件处理函数
|
||||
* @param val 新的页码
|
||||
*/
|
||||
const currentChangeHandle = (val: number) => {
|
||||
// 修改state.pagination中的current属性
|
||||
state.pagination!.current = val;
|
||||
// 再次发起查询操作
|
||||
query();
|
||||
};
|
||||
|
||||
// 排序触发事件
|
||||
const sortChangeHandle = (column: any) => {
|
||||
const prop = other.toUnderline(column.prop);
|
||||
if (column.order === 'descending') {
|
||||
state.descs?.push(prop);
|
||||
if (state.ascs!.includes(prop)) {
|
||||
state.ascs?.splice(state.ascs.indexOf(prop), 1);
|
||||
}
|
||||
} else if (column.order === 'ascending') {
|
||||
state.ascs?.push(prop);
|
||||
if (state.descs!.includes(prop)) {
|
||||
state.descs?.splice(state.descs.indexOf(prop), 1);
|
||||
}
|
||||
} else {
|
||||
if (state.ascs!.includes(prop)) {
|
||||
state.ascs?.splice(state.ascs.indexOf(prop), 1);
|
||||
}
|
||||
if (state.descs!.includes(prop)) {
|
||||
state.descs?.splice(state.descs.indexOf(prop), 1);
|
||||
}
|
||||
}
|
||||
query();
|
||||
};
|
||||
// 排序触发事件
|
||||
const sortChangeHandle = (column: any) => {
|
||||
const prop = other.toUnderline(column.prop);
|
||||
if (column.order === "descending") {
|
||||
state.descs?.push(prop);
|
||||
if (state.ascs!.includes(prop)) {
|
||||
state.ascs?.splice(state.ascs.indexOf(prop), 1);
|
||||
}
|
||||
} else if (column.order === "ascending") {
|
||||
state.ascs?.push(prop);
|
||||
if (state.descs!.includes(prop)) {
|
||||
state.descs?.splice(state.descs.indexOf(prop), 1);
|
||||
}
|
||||
} else {
|
||||
if (state.ascs!.includes(prop)) {
|
||||
state.ascs?.splice(state.ascs.indexOf(prop), 1);
|
||||
}
|
||||
if (state.descs!.includes(prop)) {
|
||||
state.descs?.splice(state.descs.indexOf(prop), 1);
|
||||
}
|
||||
}
|
||||
query();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取数据列表,并可选择是否刷新当前页码
|
||||
* 刷新后不跳转第一页,则入参 getDataList(false)
|
||||
* @param refresh 是否刷新当前页码
|
||||
*/
|
||||
const getDataList = async (refresh?: any) => {
|
||||
// 如果需要刷新,则将state.pagination.current重置为1
|
||||
if (refresh !== false) {
|
||||
state.pagination!.current = 1;
|
||||
}
|
||||
// 再次发起查询操作
|
||||
await query();
|
||||
};
|
||||
/**
|
||||
* 获取数据列表,并可选择是否刷新当前页码
|
||||
* 刷新后不跳转第一页,则入参 getDataList(false)
|
||||
* @param refresh 是否刷新当前页码
|
||||
*/
|
||||
const getDataList = async (refresh?: any) => {
|
||||
// 如果需要刷新,则将state.pagination.current重置为1
|
||||
if (refresh !== false) {
|
||||
state.pagination!.current = 1;
|
||||
}
|
||||
// 再次发起查询操作
|
||||
await query();
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param url 文件下载地址
|
||||
* @param query 请求参数(可能包含token)
|
||||
* @param fileName 文件名
|
||||
* @returns 返回一个Promise对象,用于异步处理结果
|
||||
*/
|
||||
const downBlobFile = (url: string, query: any, fileName: string) => {
|
||||
return other.downBlobFile(url, query, fileName);
|
||||
};
|
||||
/**
|
||||
* 下载文件
|
||||
* @param url 文件下载地址
|
||||
* @param query 请求参数(可能包含token)
|
||||
* @param fileName 文件名
|
||||
* @returns 返回一个Promise对象,用于异步处理结果
|
||||
*/
|
||||
const downBlobFile = (url: string, query: any, fileName: string) => {
|
||||
return other.downBlobFile(url, query, fileName);
|
||||
};
|
||||
|
||||
const postDownBlobFile = (url: string, body: any, fileName: string) => {
|
||||
return other.poseDownBlobFile(url, body, fileName);
|
||||
};
|
||||
const postDownBlobFile = (url: string, body: any, fileName: string) => {
|
||||
return other.poseDownBlobFile(url, body, fileName);
|
||||
};
|
||||
|
||||
/**
|
||||
* 定义表格通用样式
|
||||
* @returns css
|
||||
*/
|
||||
const tableStyle: TableStyle = {
|
||||
cellStyle: { textAlign: 'center' },
|
||||
headerCellStyle: {
|
||||
textAlign: 'center',
|
||||
background: 'var(--el-table-row-hover-bg-color)',
|
||||
color: 'var(--el-text-color-primary)',
|
||||
},
|
||||
};
|
||||
/**
|
||||
* 定义表格通用样式
|
||||
* @returns css
|
||||
*/
|
||||
const tableStyle: TableStyle = {
|
||||
cellStyle: { textAlign: "center" },
|
||||
headerCellStyle: {
|
||||
textAlign: "center",
|
||||
background: "var(--el-table-row-hover-bg-color)",
|
||||
color: "var(--el-text-color-primary)",
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
tableStyle,
|
||||
getDataList,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
postDownBlobFile
|
||||
};
|
||||
return {
|
||||
tableStyle,
|
||||
getDataList,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
postDownBlobFile,
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<draggable-dept-tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含"
|
||||
:defaultTreeWidth="20" :nodeClickable="false">
|
||||
:defaultTreeWidth="15" :nodeClickable="false">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="editable-area" :class="{ 'is-hover': hover }" @mouseenter="hover = true" @mouseleave="hover = false">
|
||||
<slot></slot>
|
||||
<div class="edit-btn-wrap" v-if="hover">
|
||||
<el-button type="primary" size="small" @click="$emit('edit')">编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const hover = ref(false)
|
||||
|
||||
defineEmits(['edit'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editable-area {
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.editable-area.is-hover {
|
||||
outline: 2px dashed var(--el-color-primary);
|
||||
outline-offset: 4px;
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
}
|
||||
.edit-btn-wrap {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: -12px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="900px" append-to-body destroy-on-close>
|
||||
<div class="list-editor">
|
||||
<el-button type="primary" plain style="margin-bottom: 16px;" @click="handleAdd">添加一项</el-button>
|
||||
<el-collapse v-model="activeName" accordion @change="handleChangeActive">
|
||||
<el-collapse-item v-for="(item, index) in listData" :key="index" :name="index">
|
||||
<template #title>
|
||||
<div
|
||||
style="display: flex; justify-content: space-between; width: 100%; align-items: center; padding-right: 16px;">
|
||||
<span>第 {{ index + 1 }} 项</span>
|
||||
<el-button type="danger" link @click.stop="handleRemove(index)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="item" label-width="100px" style="margin-top: 16px;">
|
||||
<template v-for="col in columns" :key="col.prop">
|
||||
<el-form-item :label="col.label">
|
||||
<template v-if="col.type === 'richtext'">
|
||||
<div style="width: 100%; border: 1px solid #dcdfe6; border-radius: 4px;"
|
||||
v-if="visible && activeName === index">
|
||||
<Editor :getHtml="item[col.prop]"
|
||||
@update:getHtml="val => { if (val) item[col.prop] = val }" height="200" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="col.type === 'textarea'">
|
||||
<el-input type="textarea" v-model="item[col.prop]" :rows="3" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input v-model="item[col.prop]" />
|
||||
</template>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import Editor from '/@/components/Editor/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '编辑列表'
|
||||
},
|
||||
columns: {
|
||||
type: Array as () => any[],
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
const visible = ref(false)
|
||||
const listData = ref<any[]>([])
|
||||
const activeName = ref(0)
|
||||
|
||||
const handleChangeActive = (val: number | string) => {
|
||||
// 强制触发重新渲染
|
||||
const currentList = cloneDeep(listData.value)
|
||||
listData.value = []
|
||||
nextTick(() => {
|
||||
listData.value = currentList
|
||||
activeName.value = val as number
|
||||
})
|
||||
}
|
||||
|
||||
const open = (initialData: any[]) => {
|
||||
listData.value = cloneDeep(initialData)
|
||||
activeName.value = 0
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
const newItem: any = {}
|
||||
props.columns.forEach(col => {
|
||||
newItem[col.prop] = ''
|
||||
})
|
||||
listData.value.push(newItem)
|
||||
activeName.value = listData.value.length - 1
|
||||
}
|
||||
|
||||
const handleRemove = (index: number) => {
|
||||
listData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', listData.value)
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list-editor {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="800px" append-to-body destroy-on-close>
|
||||
<div class="editor-container" v-if="visible">
|
||||
<Editor v-model:getHtml="content" height="400" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import Editor from '/@/components/Editor/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '编辑内容'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
const visible = ref(false)
|
||||
const content = ref('')
|
||||
|
||||
const open = (initialContent: string) => {
|
||||
content.value = initialContent
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', content.value)
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-container {
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<div class="camp-intro">
|
||||
<section class="hero-panel">
|
||||
<EditableArea class="hero-stats" @edit="openListEditor('stats', stats, statsColumns)">
|
||||
<div v-for="item in stats" :key="item.label" class="stat-card">
|
||||
<div class="stat-value">{{ item.value }}</div>
|
||||
<div class="stat-label">{{ item.label }}</div>
|
||||
<div class="stat-note" v-html="item.note"></div>
|
||||
</div>
|
||||
</EditableArea>
|
||||
<div class="hero-copy">
|
||||
<div class="hero-tag">园区介绍</div>
|
||||
<h1 class="hero-title">中国人民解放军雷达领域高等学府</h1>
|
||||
<EditableArea @edit="openRichTextEditor('heroDesc', heroDesc)">
|
||||
<div class="hero-desc" v-html="heroDesc"></div>
|
||||
</EditableArea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-grid">
|
||||
<article class="panel overview-panel">
|
||||
<div class="panel-title">学校概况</div>
|
||||
<div class="panel-line"></div>
|
||||
<EditableArea @edit="openRichTextEditor('overviewHtml', overviewHtml)">
|
||||
<div class="panel-paragraph" v-html="overviewHtml"></div>
|
||||
</EditableArea>
|
||||
</article>
|
||||
|
||||
<article class="panel feature-panel">
|
||||
<div class="panel-title">办学特色</div>
|
||||
<div class="panel-line"></div>
|
||||
<EditableArea class="feature-list" @edit="openListEditor('features', features, featuresColumns)">
|
||||
<div v-for="feature in features" :key="feature.title" class="feature-card">
|
||||
<div class="feature-index">{{ feature.index }}</div>
|
||||
<div class="feature-body">
|
||||
<div class="feature-title">{{ feature.title }}</div>
|
||||
<div class="feature-text" v-html="feature.text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</EditableArea>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="panel timeline-panel">
|
||||
<div class="panel-title">历史沿革</div>
|
||||
<div class="panel-line"></div>
|
||||
<EditableArea class="timeline" @edit="openListEditor('timeline', timeline, timelineColumns)">
|
||||
<div v-for="item in timeline" :key="item.year" class="timeline-item">
|
||||
<div class="timeline-year">{{ item.year }}</div>
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-card">
|
||||
<div class="timeline-title">{{ item.title }}</div>
|
||||
<div class="timeline-text" v-html="item.text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</EditableArea>
|
||||
</section>
|
||||
|
||||
<RichTextEditorDialog ref="richTextDialogRef" @confirm="handleRichTextConfirm" />
|
||||
<ListEditorDialog ref="listDialogRef" :columns="currentListColumns" @confirm="handleListConfirm" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useMessage } from "/@/hooks/message"
|
||||
import * as ComDhSettingApi from "/@/api/com/comDhSetting";
|
||||
|
||||
import EditableArea from './components/EditableArea.vue'
|
||||
import RichTextEditorDialog from './components/RichTextEditorDialog.vue'
|
||||
import ListEditorDialog from './components/ListEditorDialog.vue'
|
||||
|
||||
const heroDesc = ref<any>("")
|
||||
const stats = ref<any>([]);
|
||||
const overviewHtml = ref<any>("");
|
||||
const features = ref<any>([]);
|
||||
const timeline = ref<any>([]);
|
||||
|
||||
// Dialogs
|
||||
const richTextDialogRef = ref<any>(null)
|
||||
const listDialogRef = ref<any>(null)
|
||||
|
||||
const currentEditField = ref('')
|
||||
const currentListColumns = ref([])
|
||||
|
||||
const state = reactive<any>({
|
||||
loading: false,
|
||||
})
|
||||
const form = reactive({
|
||||
id: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getSetting()
|
||||
})
|
||||
|
||||
const openRichTextEditor = (field: any, content: any) => {
|
||||
currentEditField.value = field
|
||||
richTextDialogRef.value.open(content)
|
||||
}
|
||||
|
||||
const handleRichTextConfirm = async (content: any) => {
|
||||
console.log("handleRichTextConfirm-content:", content);
|
||||
if (currentEditField.value === 'heroDesc') {
|
||||
heroDesc.value = content
|
||||
} else if (currentEditField.value === 'overviewHtml') {
|
||||
overviewHtml.value = content
|
||||
}
|
||||
await onSubmit()
|
||||
}
|
||||
|
||||
const statsColumns = [
|
||||
{ prop: 'value', label: '数值' },
|
||||
{ prop: 'label', label: '标题' },
|
||||
{ prop: 'note', label: '描述(富文本)', type: 'richtext' }
|
||||
]
|
||||
|
||||
const featuresColumns = [
|
||||
{ prop: 'index', label: '序号' },
|
||||
{ prop: 'title', label: '标题' },
|
||||
{ prop: 'text', label: '描述(富文本)', type: 'richtext' }
|
||||
]
|
||||
|
||||
const timelineColumns = [
|
||||
{ prop: 'year', label: '年份' },
|
||||
{ prop: 'title', label: '标题' },
|
||||
{ prop: 'text', label: '描述(富文本)', type: 'richtext' }
|
||||
]
|
||||
|
||||
const openListEditor = (field: any, data: any, columns: any) => {
|
||||
currentEditField.value = field
|
||||
currentListColumns.value = columns
|
||||
listDialogRef.value.open(data)
|
||||
}
|
||||
|
||||
const handleListConfirm = async (data: any) => {
|
||||
console.log("handleListConfirm-data:", data);
|
||||
if (currentEditField.value === 'stats') {
|
||||
stats.value = data
|
||||
} else if (currentEditField.value === 'features') {
|
||||
features.value = data
|
||||
} else if (currentEditField.value === 'timeline') {
|
||||
timeline.value = data
|
||||
}
|
||||
await onSubmit()
|
||||
}
|
||||
|
||||
const getSetting = async () => {
|
||||
state.loading = true
|
||||
try {
|
||||
const { data } = await ComDhSettingApi.getObj()
|
||||
if (data) {
|
||||
Object.assign(form, data)
|
||||
if (data.campIntroJson) {
|
||||
const obj = JSON.parse(data.campIntroJson)
|
||||
if (obj.heroDesc) {
|
||||
heroDesc.value = obj.heroDesc
|
||||
}
|
||||
if (obj.stats) {
|
||||
stats.value = obj.stats
|
||||
}
|
||||
if (obj.overviewHtml) {
|
||||
overviewHtml.value = obj.overviewHtml
|
||||
}
|
||||
if (obj.features) {
|
||||
features.value = obj.features
|
||||
}
|
||||
if (obj.timeline) {
|
||||
timeline.value = obj.timeline
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
// 收集所有数据
|
||||
const _form = {
|
||||
// Hero 区域富文本描述
|
||||
heroDesc: heroDesc.value,
|
||||
// 顶部统计卡片数据 (数组)
|
||||
stats: stats.value,
|
||||
// 学校概况富文本内容
|
||||
overviewHtml: overviewHtml.value,
|
||||
// 办学特色列表数据 (数组)
|
||||
features: features.value,
|
||||
// 历史沿革时间轴数据 (数组)
|
||||
timeline: timeline.value
|
||||
}
|
||||
console.log("_form:", _form);
|
||||
|
||||
state.loading = true
|
||||
try {
|
||||
await ComDhSettingApi.putObj({ id: form.id, campIntroJson: JSON.stringify(_form) })
|
||||
useMessage().success('修改成功');
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
useMessage().error(error.msg);
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getSetting,
|
||||
onSubmit
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.camp-intro {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.56vw 0.62vw;
|
||||
color: #d6ebff;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(27, 102, 188, 0.18), transparent 26%),
|
||||
radial-gradient(circle at left center, rgba(18, 68, 126, 0.16), transparent 24%),
|
||||
linear-gradient(135deg, rgba(6, 18, 33, 0.96), rgba(8, 24, 44, 0.92));
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.camp-intro::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-panel,
|
||||
.panel {
|
||||
position: relative;
|
||||
border: 1px solid rgba(124, 198, 255, 0.13);
|
||||
background: linear-gradient(to right, rgba(10, 82, 154, 0.18), rgba(10, 82, 154, 0.06), rgba(10, 16, 26, 0.84));
|
||||
box-shadow: inset 0 0 22px rgba(14, 66, 124, 0.12), 0 8px 22px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(3px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-panel {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(420px, 1fr);
|
||||
gap: 1vw;
|
||||
min-height: 11.8vw;
|
||||
padding: 0.76vw 0.88vw;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.hero-panel::after,
|
||||
.panel::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(90deg, transparent, rgba(124, 198, 255, 0.06), transparent);
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.42vw;
|
||||
}
|
||||
|
||||
.hero-tag {
|
||||
width: fit-content;
|
||||
padding: 0.16vw 0.48vw;
|
||||
border: 1px solid rgba(124, 198, 255, 0.22);
|
||||
border-radius: 999px;
|
||||
font-size: 0.5vw;
|
||||
letter-spacing: 0.1vw;
|
||||
color: #9fd7ff;
|
||||
background: rgba(124, 198, 255, 0.08);
|
||||
text-shadow: 0 0 8px rgba(124, 198, 255, 0.4);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
margin: 0;
|
||||
font-size: 1.42vw;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05vw;
|
||||
color: #f4fbff;
|
||||
text-shadow: 0 0 12px rgba(124, 198, 255, 0.25);
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
margin: 0;
|
||||
max-width: 39vw;
|
||||
line-height: 1.62;
|
||||
font-size: 0.66vw;
|
||||
color: rgba(214, 235, 255, 0.82);
|
||||
}
|
||||
|
||||
.hero-desc :deep(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.5vw;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
min-height: 7.2vw;
|
||||
padding: 0.62vw 0.64vw;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(124, 198, 255, 0.12);
|
||||
background: linear-gradient(180deg, rgba(10, 40, 68, 0.82), rgba(7, 18, 31, 0.88));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.24vw;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.16vw;
|
||||
font-weight: 700;
|
||||
color: #7cc6ff;
|
||||
text-shadow: 0 0 12px rgba(124, 198, 255, 0.35);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.62vw;
|
||||
color: #f3faff;
|
||||
letter-spacing: 0.05vw;
|
||||
}
|
||||
|
||||
.stat-note {
|
||||
font-size: 0.54vw;
|
||||
line-height: 1.42;
|
||||
color: rgba(202, 238, 255, 0.68);
|
||||
}
|
||||
|
||||
.stat-note :deep(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
margin-top: 0.62vw;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
|
||||
gap: 0.62vw;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border-radius: 12px;
|
||||
padding: 0.74vw 0.78vw 0.68vw;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 0.74vw;
|
||||
font-weight: 600;
|
||||
color: #f5fbff;
|
||||
letter-spacing: 0.06vw;
|
||||
text-shadow: 0 0 10px rgba(124, 198, 255, 0.3);
|
||||
}
|
||||
|
||||
.panel-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin: 0.45vw 0 0.55vw;
|
||||
background: linear-gradient(90deg, rgba(124, 198, 255, 0.55), rgba(10, 82, 154, 0.4), transparent);
|
||||
}
|
||||
|
||||
.panel-paragraph :deep(p) {
|
||||
margin: 0 0 0.46vw;
|
||||
font-size: 0.64vw;
|
||||
line-height: 1.62;
|
||||
color: rgba(214, 235, 255, 0.82);
|
||||
text-indent: 2em;
|
||||
}
|
||||
|
||||
.panel-paragraph :deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5vw;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
display: grid;
|
||||
grid-template-columns: 2.2vw 1fr;
|
||||
gap: 0.58vw;
|
||||
align-items: start;
|
||||
padding: 0.58vw 0.62vw;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(124, 198, 255, 0.1);
|
||||
background: linear-gradient(135deg, rgba(9, 31, 56, 0.86), rgba(7, 18, 31, 0.78));
|
||||
}
|
||||
|
||||
.feature-index {
|
||||
width: 2.2vw;
|
||||
height: 2.2vw;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.7vw;
|
||||
font-weight: 700;
|
||||
color: #7cc6ff;
|
||||
background: radial-gradient(circle, rgba(124, 198, 255, 0.24), rgba(10, 37, 62, 0.88));
|
||||
box-shadow: 0 0 12px rgba(124, 198, 255, 0.14);
|
||||
}
|
||||
|
||||
.feature-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
margin-bottom: 0.2vw;
|
||||
font-size: 0.68vw;
|
||||
font-weight: 600;
|
||||
color: #f4fbff;
|
||||
}
|
||||
|
||||
.feature-text {
|
||||
line-height: 1.5;
|
||||
font-size: 0.6vw;
|
||||
color: rgba(202, 238, 255, 0.72);
|
||||
}
|
||||
|
||||
.feature-text :deep(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.timeline-panel {
|
||||
margin-top: 0.62vw;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.62vw;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
padding-top: 0.68vw;
|
||||
}
|
||||
|
||||
.timeline-year {
|
||||
margin-bottom: 0.4vw;
|
||||
font-size: 0.72vw;
|
||||
font-weight: 700;
|
||||
color: #7cc6ff;
|
||||
letter-spacing: 0.06vw;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0.1vw;
|
||||
width: 0.44vw;
|
||||
height: 0.44vw;
|
||||
border-radius: 50%;
|
||||
background: #7cc6ff;
|
||||
box-shadow: 0 0 12px rgba(124, 198, 255, 0.6);
|
||||
}
|
||||
|
||||
.timeline-card {
|
||||
min-height: 6.4vw;
|
||||
padding: 0.62vw;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(124, 198, 255, 0.1);
|
||||
background: linear-gradient(180deg, rgba(9, 31, 56, 0.86), rgba(7, 18, 31, 0.8));
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
margin-bottom: 0.22vw;
|
||||
font-size: 0.66vw;
|
||||
font-weight: 600;
|
||||
color: #f4fbff;
|
||||
}
|
||||
|
||||
.timeline-text {
|
||||
line-height: 1.54;
|
||||
font-size: 0.58vw;
|
||||
color: rgba(202, 238, 255, 0.72);
|
||||
}
|
||||
|
||||
.timeline-text :deep(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.hero-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.content-grid,
|
||||
.timeline {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -29,6 +29,9 @@
|
||||
<div class="w-full" v-if="state.currentTab === 'DEVICE_ALARM'">
|
||||
<DeviceAlarmSetting ref="deviceAlarmSettingRef" />
|
||||
</div>
|
||||
<div class="w-full" v-if="state.currentTab === 'CAMP_INTRO'">
|
||||
<CampIntro ref="campIntro" v-if="state.currentTab === 'CAMP_INTRO'" />
|
||||
</div>
|
||||
<div class="h-20" />
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
@@ -39,6 +42,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="deviceLog">
|
||||
const CampIntro = defineAsyncComponent(() => import('./campIntro/index.vue'));
|
||||
const CarSetting = defineAsyncComponent(() => import('./car/car-setting.vue'));
|
||||
const CleaningData = defineAsyncComponent(() => import('./cleaningData/cleaning-data.vue'));
|
||||
const DoorSetting = defineAsyncComponent(() => import('./door/door-setting.vue'));
|
||||
@@ -82,9 +86,14 @@ const data = {
|
||||
label: "设备报警配置",
|
||||
name: "DEVICE_ALARM",
|
||||
},
|
||||
{
|
||||
label: "园区介绍",
|
||||
name: "CAMP_INTRO",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const campIntro = ref()
|
||||
const doorSetting = ref()
|
||||
const carSetting = ref()
|
||||
const panelSetting = ref()
|
||||
@@ -135,5 +144,9 @@ function onSubmit() {
|
||||
deviceAlarmSettingRef.value.onSubmit()
|
||||
return
|
||||
}
|
||||
if (state.currentTab == "CAMP_INTRO") {
|
||||
campIntro.value.onSubmit()
|
||||
return
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<draggable-org-Tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含"
|
||||
:defaultTreeWidth="20" :nodeClickable="false">
|
||||
:defaultTreeWidth="15" :nodeClickable="false">
|
||||
<template #default>
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<draggable-org-Tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含" :defaultTreeWidth="20"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含" :defaultTreeWidth="15"
|
||||
:nodeClickable="false">
|
||||
<template #default>
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
|
||||
@@ -154,6 +154,7 @@ const resetQuery = () => {
|
||||
function openDialog(query: any) {
|
||||
state.queryForm = query
|
||||
state.queryForm.isDkErrorList = true
|
||||
state.queryForm.allFindAlarmTypeList = true
|
||||
visible.value = true
|
||||
getDataList();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="User" size="small" type="primary" @click="doPersonClockConfig">
|
||||
人员打卡配置
|
||||
</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="false" @exportExcel="exportExcel"
|
||||
class="ml10 mr20" style="float: right;" @queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
@@ -23,7 +26,6 @@
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle"
|
||||
@sort-change="sortChangeHandle" show-summary :summary-method="getSummaries">
|
||||
<el-table-column type="selection" width="80" align="center" />
|
||||
<el-table-column type="index" label="#" width="80" />
|
||||
<el-table-column min-width="140" label="日期">
|
||||
<template #default="{ row }">
|
||||
@@ -165,6 +167,10 @@ const selectionChangHandle = (objs: { id: string }[]) => {
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
const doPersonClockConfig = () => {
|
||||
push({ path: "/door/personClockConfig/index" })
|
||||
}
|
||||
|
||||
const getSummaries = (param: any) => {
|
||||
const { columns, data } = param
|
||||
const totalColumns = ["reignCount", "carReignCount", "totalReignCount", "notReignCount", "lateCount", "earlyCount"]
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<draggable-dept-tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含"
|
||||
:defaultTreeWidth="15" :nodeClickable="false">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item style="width: 250px;" label="姓名" prop="name">
|
||||
<el-input v-model="state.queryForm.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px;" label="手机号" prop="phone">
|
||||
<el-input v-model="state.queryForm.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px;" label="证件号码" prop="paperNumber">
|
||||
<el-input v-model="state.queryForm.paperNumber" placeholder="请输入证件号码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">
|
||||
{{ $t('common.resetBtn') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div v-auth="'admin_personClockConfig_change'" class="mb8" style="width: 100%">
|
||||
<el-button icon="CircleCheck" type="primary" class="ml10" :disabled="multiple"
|
||||
v-auth="'admin_sysPerson_add'" @click="handleChangeClockConfig(true)">
|
||||
加入打卡
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" icon="CircleClose" type="primary"
|
||||
v-auth="'admin_sysPerson_del'" @click="handleChangeClockConfig(false)">
|
||||
移除打卡
|
||||
</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="'admin_sysPerson_export'"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
|
||||
@queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table ref="tableRef" :data="state.dataList" v-loading="state.loading" row-key="code" border
|
||||
size="small" :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle" @sort-change="sortChangeHandle">
|
||||
<el-table-column type="selection" width="40" align="center" :reserve-selection="true" />
|
||||
<el-table-column type="index" label="#" width="64" />
|
||||
<el-table-column width="100" label="是否加入打卡">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.clockEnable === '1' ? 'success' : 'danger'">
|
||||
{{ row.clockEnable === '1' ? '已加入' :
|
||||
'未加入' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="250" label="人员信息">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-2 py-1">
|
||||
<div class="w-[52px] shrink-0">
|
||||
<preview-image width="42px" height="42px" :src="row.avatar" />
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col justify-center gap-1 overflow-hidden pr-2">
|
||||
<div class="flex items-center gap-1 overflow-hidden leading-5">
|
||||
<span class="truncate font-medium">{{ row.name }}</span>
|
||||
<span v-if="row.age" class="shrink-0 text-xs text-gray-500">{{ row.age
|
||||
}}岁</span>
|
||||
<tag-sex :code="row.sex" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-xs text-gray-500 leading-5">
|
||||
<span class="shrink-0">编码</span>
|
||||
<div class="flex-1 text-ellipsis text-start">{{ row.code || '--' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="220" label="证件信息">
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
class="flex flex-col justify-start items-start gap-1 py-1 text-start text-xs leading-5">
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-16 shrink-0 text-gray-500">证件类型</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">
|
||||
<dict-tag :options="dh_paper_type" :value="row.paperType" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-16 shrink-0 text-gray-500">证件号码</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">{{ row.paperNumber || '--' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-16 shrink-0 text-gray-500">证件地址</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">{{ row.paperAddress || '--' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deptName" width="132" label="部门名称" show-overflow-tooltip />
|
||||
<el-table-column prop="phone" width="140" label="手机号码" show-overflow-tooltip />
|
||||
<el-table-column prop="email" width="170" label="电子邮箱" show-overflow-tooltip />
|
||||
<el-table-column prop="status" width="110" label="人员状态" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<tag-user-status :code="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isSync" width="100" label="同步">
|
||||
<template #default="{ row }">
|
||||
<tag-sync :isSync="row.isSync == 1" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="同步配置" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="user_sync_config" :value="row.syncConfigType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-auth="'admin_personClockConfig_change'" label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 py-1 text-xs">
|
||||
<el-button style="margin: 0; padding: 0;" icon="CircleCheck" text type="primary"
|
||||
v-auth="'admin_personClockConfig_change'"
|
||||
@click="handleChangeClockConfig(true, [row])">
|
||||
加入打卡
|
||||
</el-button>
|
||||
<el-button style="margin: 0; padding: 0;" icon="CircleClose" text type="primary"
|
||||
v-auth="'admin_personClockConfig_change'"
|
||||
@click="handleChangeClockConfig(false, [row])">
|
||||
移除打卡
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
|
||||
v-bind="state.pagination" />
|
||||
</draggable-dept-tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysPerson">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList } from '/@/api/sys/sysPerson'
|
||||
import { change } from '/@/api/sys/sysPersonClockConfig'
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import dayjs from "dayjs";
|
||||
import other from '/@/utils/other'
|
||||
|
||||
// 定义查询字典
|
||||
const { dh_paper_type, user_sync_config } = useDict('dh_paper_type', 'user_sync_config')
|
||||
|
||||
// 定义变量内容
|
||||
const tableRef = ref()
|
||||
// 搜索变量
|
||||
const queryRef = ref()
|
||||
const showSearch = ref(true)
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any
|
||||
const multiple = ref(true)
|
||||
const draggableDepTreeRef = ref()
|
||||
const selectionRowMap = ref(new Map<string, any>())
|
||||
const isRestoringSelection = ref(false)
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList: baseGetDataList,
|
||||
postDownBlobFile,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
const syncSelectionState = () => {
|
||||
selectObjs.value = Array.from(selectionRowMap.value.values()).map((item: any) => item.id)
|
||||
multiple.value = selectionRowMap.value.size === 0
|
||||
}
|
||||
|
||||
const clearSelectionState = () => {
|
||||
selectionRowMap.value.clear()
|
||||
selectObjs.value = []
|
||||
multiple.value = true
|
||||
tableRef.value?.clearSelection()
|
||||
}
|
||||
|
||||
const restoreSelection = async () => {
|
||||
if (!tableRef.value) return
|
||||
const currentPageRows = state.dataList ?? []
|
||||
isRestoringSelection.value = true
|
||||
tableRef.value.clearSelection()
|
||||
currentPageRows.forEach((row: any) => {
|
||||
if (selectionRowMap.value.has(row.code)) {
|
||||
tableRef.value.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
await nextTick()
|
||||
isRestoringSelection.value = false
|
||||
}
|
||||
|
||||
const getDataList = async (refresh?: any) => {
|
||||
await baseGetDataList(refresh)
|
||||
await nextTick()
|
||||
await restoreSelection()
|
||||
}
|
||||
|
||||
const currentChangeHandle = async (val: number) => {
|
||||
state.pagination!.current = val
|
||||
await getDataList(false)
|
||||
}
|
||||
|
||||
const sizeChangeHandle = async (val: number) => {
|
||||
state.pagination!.size = val
|
||||
await getDataList(false)
|
||||
}
|
||||
|
||||
const sortChangeHandle = async (column: any) => {
|
||||
const prop = other.toUnderline(column.prop)
|
||||
if (column.order === 'descending') {
|
||||
state.descs = [prop]
|
||||
state.ascs = state.ascs?.filter((item) => item !== prop) ?? []
|
||||
} else if (column.order === 'ascending') {
|
||||
state.ascs = [prop]
|
||||
state.descs = state.descs?.filter((item) => item !== prop) ?? []
|
||||
} else {
|
||||
state.ascs = state.ascs?.filter((item) => item !== prop) ?? []
|
||||
state.descs = state.descs?.filter((item) => item !== prop) ?? []
|
||||
}
|
||||
await getDataList(false)
|
||||
}
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields()
|
||||
// 清空多选
|
||||
clearSelectionState()
|
||||
// 清空树状选择
|
||||
state.queryForm.deptId = ""
|
||||
state.queryForm.deptCodes = []
|
||||
draggableDepTreeRef.value.resetData()
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
const timeSuffix = dayjs().format('YYYY-MM-DD_HH-mm-ss')
|
||||
postDownBlobFile('/admin/sysPerson/export', Object.assign(state.queryForm, { ids: selectObjs }), `人员信息管理_${timeSuffix}.xlsx`)
|
||||
}
|
||||
|
||||
// 多选事件
|
||||
const selectionChangHandle = (rows: any[]) => {
|
||||
if (isRestoringSelection.value) return
|
||||
const currentPageRows = state.dataList ?? []
|
||||
const currentPageCodeSet = new Set(currentPageRows.map((item: any) => item.code))
|
||||
const selectedCodeSet = new Set(rows.map((item: any) => item.code))
|
||||
|
||||
currentPageCodeSet.forEach((code) => {
|
||||
if (!selectedCodeSet.has(code)) {
|
||||
selectionRowMap.value.delete(code)
|
||||
}
|
||||
})
|
||||
|
||||
rows.forEach((row: any) => {
|
||||
if (row.code) {
|
||||
selectionRowMap.value.set(row.code, row)
|
||||
}
|
||||
})
|
||||
|
||||
syncSelectionState()
|
||||
};
|
||||
|
||||
// 树节点选择时
|
||||
const handleGetSelectNode = (curClickedNode: any, checkedTreeNodes: any, isDeepTree: any) => {
|
||||
state.queryForm.deptId = curClickedNode.id
|
||||
state.queryForm.deptCodes = checkedTreeNodes.map((item: any) => item.id)
|
||||
state.queryForm.isInclude = !isDeepTree
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleChangeClockConfig = async (enable: boolean, rows?: any[]) => {
|
||||
const selectedRows = rows?.length ? rows : Array.from(selectionRowMap.value.values())
|
||||
const userCodeList = selectedRows
|
||||
.map((item: any) => item.code)
|
||||
.filter((item: string) => !!item)
|
||||
|
||||
if (!userCodeList.length) {
|
||||
useMessage().error('请先选择需要操作的人员')
|
||||
return
|
||||
}
|
||||
|
||||
const actionText = enable ? '加入打卡' : '移除打卡'
|
||||
const userNameListText = selectedRows
|
||||
.map((item: any) => item.name || item.code)
|
||||
.filter((item: string) => !!item)
|
||||
.join('、')
|
||||
|
||||
try {
|
||||
await useMessageBox().confirm(`是否确认${actionText}${userCodeList.length}名人员?\n${userNameListText}`);
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await change({ userCodeList, enable: enable ? '1' : '0' })
|
||||
if (!rows?.length) {
|
||||
clearSelectionState()
|
||||
}
|
||||
await getDataList(false)
|
||||
useMessage().success(`${actionText}成功`)
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text-ellipsis {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -117,7 +117,7 @@ const getThemeConfig = computed(() => {
|
||||
*/
|
||||
const jumpAdminPanel = () => {
|
||||
const isDev = import.meta.env.MODE == "development"
|
||||
const baseAdminUrl = isDev ? 'http://localhost:5173/device' : 'http://123.157.250.162:7001/device';
|
||||
const baseAdminUrl = isDev ? 'http://localhost:5173/device' : 'http://192.168.50.10:7001/device';
|
||||
const roleCodes = useUserInfoStore.userInfos?.roleCodes ?? []
|
||||
if (!roleCodes || !roleCodes.length) {
|
||||
openPlanUrl(baseAdminUrl, true)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<draggable-dept-tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含" :defaultTreeWidth="20"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含" :defaultTreeWidth="15"
|
||||
:nodeClickable="false">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
@@ -39,83 +39,83 @@
|
||||
class="ml10 mr20" style="float: right;" @queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border size="small"
|
||||
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle" @sort-change="sortChangeHandle">
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="80" />
|
||||
<el-table-column width="280" label="人员信息">
|
||||
<el-table-column type="index" label="#" width="64" />
|
||||
<el-table-column width="250" label="人员信息">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-[60px]">
|
||||
<preview-image width="50px" height="50px" :src="row.avatar" />
|
||||
<div class="flex items-center gap-2 py-1">
|
||||
<div class="w-[52px] shrink-0">
|
||||
<preview-image width="42px" height="42px" :src="row.avatar" />
|
||||
</div>
|
||||
<div class="flex-1 flex-col justify-center pr-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span> {{ row.name }}</span>
|
||||
<span v-if="row.age"> {{ row.age }}岁 </span>
|
||||
<div class="flex flex-1 flex-col justify-center gap-1 overflow-hidden pr-2">
|
||||
<div class="flex items-center gap-1 overflow-hidden leading-5">
|
||||
<span class="truncate font-medium">{{ row.name }}</span>
|
||||
<span v-if="row.age" class="shrink-0 text-xs text-gray-500">{{ row.age }}岁</span>
|
||||
<tag-sex :code="row.sex" />
|
||||
</div>
|
||||
<div class="h-2" />
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 text-ellipsis text-start"> {{ row.code }}</div>
|
||||
<div class="flex items-center gap-1 text-xs text-gray-500 leading-5">
|
||||
<span class="shrink-0">编码</span>
|
||||
<div class="flex-1 text-ellipsis text-start">{{ row.code || '--' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="250" label="人员编码">
|
||||
<el-table-column width="220" label="证件信息">
|
||||
<template #default="{ row }">
|
||||
<div class="flex-col justify-start items-start text-start">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-20">证件类型:</div>
|
||||
<div class="flex flex-col justify-start items-start gap-1 py-1 text-start text-xs leading-5">
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-16 shrink-0 text-gray-500">证件类型</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">
|
||||
<dict-tag :options="dh_paper_type" :value="row.paperType" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-20">证件号码:</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">{{ row.paperNumber }}</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-16 shrink-0 text-gray-500">证件号码</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">{{ row.paperNumber || '--' }}</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-20">证件地址:</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">{{ row.paperAddress }}</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-16 shrink-0 text-gray-500">证件地址</div>
|
||||
<div class="flex-1 overflow-hidden text-ellipsis">{{ row.paperAddress || '--' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deptName" width="150" label="部门名称" show-overflow-tooltip />
|
||||
<el-table-column prop="phone" width="160" label="手机号码" show-overflow-tooltip />
|
||||
<el-table-column prop="email" width="150" label="电子邮箱" show-overflow-tooltip />
|
||||
<el-table-column prop="status" width="150" label="人员状态" show-overflow-tooltip>
|
||||
<el-table-column prop="deptName" width="132" label="部门名称" show-overflow-tooltip />
|
||||
<el-table-column prop="phone" width="140" label="手机号码" show-overflow-tooltip />
|
||||
<el-table-column prop="email" width="170" label="电子邮箱" show-overflow-tooltip />
|
||||
<el-table-column prop="status" width="110" label="人员状态" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<tag-user-status :code="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isSync" width="150" label="是否为同步">
|
||||
<el-table-column prop="isSync" width="100" label="同步">
|
||||
<template #default="{ row }">
|
||||
<tag-sync :isSync="row.isSync == 1" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数据同步配置" min-width="150">
|
||||
<el-table-column label="同步配置" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="user_sync_config" :value="row.syncConfigType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right"
|
||||
<el-table-column label="操作" width="170" fixed="right"
|
||||
v-auths="['admin_sysPerson_edit', 'admin_sysPerson_del']">
|
||||
<template #default="{ row }">
|
||||
<div class="grid grid-cols-2 gap-2 w-full">
|
||||
<el-button style="margin: 0;" icon="Reading" text type="primary" v-auth="'admin_sysPerson_edit'"
|
||||
@click="syncConfigFormRef.openDialog(row.id)">
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 py-1 text-xs">
|
||||
<el-button style="margin: 0; padding: 0;" icon="Reading" text type="primary"
|
||||
v-auth="'admin_sysPerson_edit'" @click="syncConfigFormRef.openDialog(row.id)">
|
||||
同步设置
|
||||
</el-button>
|
||||
<el-button style="margin: 0;" icon="edit-pen" text type="primary" v-auth="'admin_sysPerson_edit'"
|
||||
@click="formDialogRef.openDialog(row.id)">
|
||||
<el-button style="margin: 0; padding: 0;" icon="edit-pen" text type="primary"
|
||||
v-auth="'admin_sysPerson_edit'" @click="formDialogRef.openDialog(row.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button style="margin: 0;" icon="delete" text type="primary" v-auth="'admin_sysPerson_del'"
|
||||
@click="handleDelete([row.id])">
|
||||
<el-button style="margin: 0; padding: 0;" icon="delete" text type="primary"
|
||||
v-auth="'admin_sysPerson_del'" @click="handleDelete([row.id])">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user