| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- <template>
- <el-card>
- <template #header>
- <div class="card-header">
- <PlusForm
- :key="queryKey"
- v-model="queryState"
- :group="queryGroup"
- label-position="right"
- >
- <template #footer="{}">
- <div></div>
- </template>
- <template #plus-group-one="scoped">
- <div>
- <el-date-picker
- v-model="queryState.queryTime"
- type="datetimerange"
- :shortcuts="queryTimeShortCuts"
- range-separator="至"
- start-placeholder="开始日期时间"
- end-placeholder="结束日期时间"
- :popper-options="{
- placement: 'bottom-start'
- }"
- :size="dynamicSize"
- :disabled="size === 'disabled'"
- />
- <el-button
- style="margin-left: 10px"
- type="primary"
- :icon="useRenderIcon(Search)"
- @click="handleQuery"
- >查询</el-button
- >
- <el-button
- :icon="useRenderIcon(IconDownLoad)"
- @click="handleExport"
- >导出到Excel</el-button
- >
- <el-button type="primary" @click="isHideInGroup = !isHideInGroup">
- {{ isHideInGroup ? "显示筛选器" : "隐藏筛选器" }}
- </el-button>
- <el-button
- :icon="useRenderIcon(IconUpload)"
- @click="updateAttends"
- >更新到服务器</el-button
- >
- </div>
- </template>
- </PlusForm>
- </div>
- </template>
- <div>
- <pure-table
- align-whole="center"
- :header-cell-style="{
- background: 'var(--el-fill-color-light)',
- color: 'var(--el-text-color-primary)'
- }"
- :columns="columns"
- :table-key="tableKey"
- border
- adaptive
- :adaptiveConfig="adaptiveConfig"
- showOverflowTooltip
- :loading="false"
- :loading-config="loadingConfig"
- :data="
- dataFilter.slice(
- (pagination.currentPage - 1) * pagination.pageSize,
- pagination.currentPage * pagination.pageSize
- )
- "
- :pagination="pagination"
- @page-size-change="onSizeChange"
- @page-current-change="onCurrentChange"
- :has-toolbar="true"
- >
- <template #toolbar>
- <el-button plain size="small">查看日志</el-button>
- <el-button plain size="small">导出数据</el-button>
- <el-button type="primary" size="small">创建应用</el-button>
- </template>
- <template #operation="{ row, index }">
- <div v-if="!editMap[index]?.editable">
- <el-button
- class="reset-margin"
- link
- type="primary"
- :icon="useRenderIcon(Delete)"
- @click="onDel(row)"
- >
- 删除
- </el-button>
- <el-button
- class="reset-margin"
- link
- type="primary"
- @click="onEdit(row, index)"
- :icon="useRenderIcon(EditFill)"
- >
- 修改
- </el-button>
- </div>
- <div v-if="editMap[index]?.editable">
- <el-button
- class="reset-margin"
- link
- type="primary"
- @click="onSave(index)"
- :icon="useRenderIcon(SaveFill)"
- >
- 保存
- </el-button>
- <el-button
- class="reset-margin"
- link
- @click="onCancel(index)"
- :icon="useRenderIcon(Close)"
- >
- 取消
- </el-button>
- </div>
- </template>
- </pure-table>
- </div>
- </el-card>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, computed, watch } from "vue";
- import { useColumns } from "./columns";
- import {
- type PlusColumn,
- type FieldValues,
- PlusForm,
- PlusFormGroupRow
- } from "plus-pro-components";
- import { useRenderIcon } from "@/components/ReIcon/src/hooks";
- import AddFill from "~icons/ep/plus";
- import Delete from "~icons/ep/delete";
- import EditFill from "~icons/ep/edit";
- import SaveFill from "~icons/bi/save";
- import Close from "~icons/ep/close";
- import Search from "~icons/ep/search";
- import IconDownLoad from "~icons/ep/download";
- import IconUpload from "~icons/ep/upload";
- import { ElNotification, ElMessageBox } from "element-plus";
- import * as XLSX from "xlsx";
- import Attend from "@/model/attend";
- import {
- fetchHisAttend,
- deleteHisAttend,
- modifyHisAttends
- } from "@/api/attend";
- import { debounce } from "lodash";
- const queryTimeShortCuts = [
- {
- text: "今天",
- value: () => {
- const now = new Date();
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
- const end = new Date(
- now.getFullYear(),
- now.getMonth(),
- now.getDate() + 1
- );
- return [start, end];
- }
- },
- {
- text: "昨天",
- value: () => {
- const now = new Date();
- const start = new Date(
- now.getFullYear(),
- now.getMonth(),
- now.getDate() - 1
- );
- const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());
- return [start, end];
- }
- },
- {
- text: "本周",
- value: () => {
- const now = new Date();
- const start = new Date(
- now.getFullYear(),
- now.getMonth(),
- now.getDate() - now.getDay()
- );
- const end = new Date(
- now.getFullYear(),
- now.getMonth(),
- now.getDate() - now.getDay() + 6
- );
- return [start, end];
- }
- },
- {
- text: "本月",
- value: () => {
- const now = new Date();
- const start = new Date(now.getFullYear(), now.getMonth(), 1);
- const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
- return [start, end];
- }
- }
- ];
- const isHideInGroup = ref(true);
- const QC_IDX_PROJ = 0;
- const queryGroup: PlusFormGroupRow[] = [
- {
- title: "记录搜索",
- icon: Search,
- // 自动生成 plus-group-one 插槽,v0.1.25 新增
- name: "one",
- // 当plus-group-one 插槽存在时,这里的配置将不会生效
- columns: [
- {
- label: "名称",
- prop: "name"
- }
- ]
- },
- {
- title: "记录筛选",
- // icon: Calendar,
- name: "two",
- hideInGroup: computed(() => {
- return isHideInGroup.value;
- }),
- columns: [
- {
- label: "项目名称",
- width: 120,
- prop: "queryProjectName",
- valueType: "input",
- colProps: {
- span: 6
- }
- },
- {
- label: "区域名称",
- width: 120,
- prop: "queryLocationName",
- valueType: "input",
- colProps: {
- span: 6
- }
- },
- {
- label: "人员名称",
- width: 120,
- prop: "queryPersonName",
- valueType: "input",
- colProps: {
- span: 6
- }
- },
- {
- label: "电话",
- width: 120,
- prop: "queryPhone",
- valueType: "input",
- colProps: {
- span: 6
- }
- }
- ]
- }
- ];
- const dynamicSize = ref();
- const queryState = ref({
- queryTime: [
- new Date(
- new Date().getFullYear(),
- new Date().getMonth(),
- new Date().getDate()
- ),
- new Date(
- new Date().getFullYear(),
- new Date().getMonth(),
- new Date().getDate() + 1
- )
- ],
- queryProjectName: "",
- queryLocationName: "",
- queryPersonName: "",
- queryPhone: ""
- });
- const queryKey = ref(0);
- // 防抖处理,500ms内连续输入只执行一次
- const debouncedDoFilter = debounce(() => {
- doFilter();
- }, 200);
- // 监听变化并使用防抖处理
- watch(
- [
- () => queryState.value.queryPersonName,
- () => queryState.value.queryProjectName,
- () => queryState.value.queryLocationName,
- () => queryState.value.queryPhone
- ],
- debouncedDoFilter
- );
- const projects = ref([]);
- const {
- editMap,
- columns,
- dataList,
- dataFilter,
- pagination,
- loadingConfig,
- adaptiveConfig,
- getProjectName,
- getLocName,
- onEdit,
- onSave,
- onCancel,
- onDel,
- onSizeChange,
- onCurrentChange
- } = useColumns(projects);
- const swEditable = ref(true);
- const tableKey = ref(0);
- const vGlobal = window.vueGlobal;
- const dlgImportExcelVisible = ref(false);
- const excelData = ref([]);
- const attends = ref([]);
- const reloadProjects = () => {
- vGlobal.refreshProjects(() => {
- projects.value = [];
- projects.value = vGlobal.vecProject.slice(0);
- console.log("reloadProjects 1", projects.value);
- queryKey.value += 1;
- });
- };
- const getAttendRecord = () => {
- const queryParams = {
- beginTime: queryState.value.queryTime[0].getTime(),
- endTime: queryState.value.queryTime[1].getTime()
- };
- fetchHisAttend(queryParams).then(res => {
- if (res.success) {
- attends.value = [];
- dataList.value = [];
- console.log("res ", res.items);
- for (const item of res.items) {
- const obj = JSON.parse(item.jstr);
- const attend = new Attend(obj);
- attends.value.push(attend);
- attend.ancAddr = Number(item["ancAddr"]) || 0;
- for (const attendItem of attend.items) {
- dataList.value.push(attendItem);
- }
- }
- doFilter();
- ElNotification.success({
- message: "查询成功"
- });
- }
- });
- };
- const doFilter = () => {
- dataFilter.value = dataList.value.filter(item => {
- if (
- queryState.value.queryPersonName.length > 0 &&
- item.name.indexOf(queryState.value.queryPersonName) < 0
- ) {
- return false;
- }
- if (
- queryState.value.queryProjectName.length > 0 &&
- getProjectName(item.projId).indexOf(queryState.value.queryProjectName) < 0
- ) {
- return false;
- }
- if (
- queryState.value.queryLocationName.length > 0 &&
- getLocName(item.projId, item.locId).indexOf(
- queryState.value.queryLocationName
- ) < 0
- ) {
- return false;
- }
- if (
- queryState.value.queryPhone.length > 0 &&
- item.phone.indexOf(queryState.value.queryPhone) < 0
- ) {
- return false;
- }
- return true;
- });
- pagination.total = dataFilter.value.length;
- // tableKey.value++;
- };
- const updateAttends = () => {
- ElMessageBox.confirm("确定更新考勤数据?更新后将无法撤回", "Warning", {
- confirmButtonText: "确认",
- cancelButtonText: "取消",
- type: "warning"
- })
- .then(() => {
- ElNotification.info({
- message: "开始更新"
- });
- const vecAttend = [];
- for (const v of attends.value) {
- vecAttend.push(v.toJson());
- }
- modifyHisAttends({ attends: vecAttend }).then(res => {
- if (res.success) {
- ElNotification.success({
- message: "更新成功"
- });
- } else {
- ElNotification.error({
- message: "更新失败"
- });
- }
- });
- })
- .catch(() => {
- // ElMessage({
- // type: 'info',
- // message: 'Delete canceled',
- // })
- });
- };
- // 导出逻辑
- const handleExport = () => {
- try {
- // 1. 处理导出数据
- const exportData = dataFilter.value.map(item => ({
- 时间: new Date(item.tm).toLocaleString(),
- 项目名称: getProjectName(item.projId),
- 区域名称: getLocName(item.projId, item.locId),
- 人员ID: item.id,
- 人员名称: item.name,
- 职位: item.job,
- 电话: item.phone,
- 标签ID: item.tagId,
- 在线状态: item.rssi != 0 ? "在线" : "离线",
- 到岗状态: item.confirm ? "到岗" : "缺岗"
- }));
- if (exportData.length === 0) {
- ElNotification.warning({ message: "没有可导出的数据" });
- return;
- }
- // 2. 创建工作表
- const worksheet = XLSX.utils.json_to_sheet(exportData);
- // 3. 计算列宽(关键步骤)
- const header = Object.keys(exportData[0]);
- const cols = header.map(key => {
- // 计算表头文字长度
- let maxLength = key.length * 2;
- // 计算数据列最大文字长度
- exportData.forEach(row => {
- const value = String(row[key] || "");
- // 中文字符宽度约为英文字符的2倍,这里做特殊处理
- const length = value.replace(/[^\x00-\xff]/g, "aa").length;
- if (length > maxLength) {
- maxLength = length;
- }
- });
- // 增加缓冲宽度(避免内容太满)
- return { width: maxLength + 2 };
- });
- // 设置列宽配置
- worksheet["!cols"] = cols;
- // 4. 创建工作簿并添加工作表
- const workbook = XLSX.utils.book_new();
- XLSX.utils.book_append_sheet(workbook, worksheet, "考勤记录");
- // 5. 生成Excel文件并下载
- const fileName = `考勤记录_${new Date().getTime()}.xlsx`;
- XLSX.writeFile(workbook, fileName);
- } catch (error) {
- console.error("导出失败:", error);
- ElNotification.error({
- message: "导出失败,请重试"
- });
- }
- };
- const handleQuery = () => {
- getAttendRecord();
- };
- onMounted(() => {
- reloadProjects();
- });
- </script>
|