ソースを参照

reactor:登录调整;封装表格组件;市民画像日期调整;

zhangchong 1 年間 前
コミット
c1ff8007cd

+ 4 - 3
src/App.vue

@@ -73,13 +73,14 @@ const openSetTingsDrawer = () => {
 };
 // 设置初始化,防止刷新时恢复默认
 onBeforeMount(async () => {
-	if (!themeConfig.value.globalTitle) {
+	// if (!themeConfig.value.globalTitle) {
 		// 获取登录页的背景图和系统名称等
 		const res: any = await loginPageInfo();
 		const globalTitle = res.result.sysName.join('|') ?? ''; // 标题名称
 		const loginImage = res.result.loginImage ? `url${res.result.loginImage}` : `url(${getImageUrl('login/bg.png')})`; // 登录页背景图
-		storesThemeConfig.setThemeConfig(Object.assign(themeConfig.value, { globalTitle, loginImage }));
-	}
+    const isLoginMessageCode = res.result.isLoginMessageCode; // 是否开启短信验证码
+		storesThemeConfig.setThemeConfig(Object.assign(themeConfig.value, { globalTitle, loginImage,isLoginMessageCode }));
+	// }
 	// 设置批量第三方 icon 图标
 	setIntroduction.cssCdn();
 	// 设置批量第三方 js

+ 11 - 0
src/api/login/index.ts

@@ -36,3 +36,14 @@ export const loginPageInfo = () => {
 		method: 'get',
 	});
 };
+/**
+ * @description 登录发送验证码
+ * @param {string} UserName  登录参数
+ *  @returns
+ */
+export const sendCode = (UserName: string) => {
+	return request({
+		url: `/api/v1/PushMessage/send_login_message_code?UserName=${UserName}`,
+		method: 'get'
+	});
+}

+ 30 - 25
src/components/ProTable/components/ColSetting.vue

@@ -1,43 +1,48 @@
 <template>
-  <!-- 列设置 -->
-  <el-drawer v-model="drawerVisible" title="列设置" size="450px">
-    <div class="table-main">
-      <el-table :data="colSetting" :border="true" row-key="prop" default-expand-all :tree-props="{ children: '_children' }">
-        <el-table-column prop="label" align="center" label="列名" />
-        <el-table-column v-slot="scope" prop="isShow" align="center" label="显示">
-          <el-switch v-model="scope.row.isShow"></el-switch>
-        </el-table-column>
-        <el-table-column v-slot="scope" prop="sortable" align="center" label="排序">
-          <el-switch v-model="scope.row.sortable"></el-switch>
-        </el-table-column>
-        <template #empty>
-          <Empty  description="暂无可配置列"/>
-        </template>
-      </el-table>
-    </div>
-  </el-drawer>
+	<!-- 列设置 -->
+	<el-drawer v-model="drawerVisible" title="列设置" size="350px">
+		<div class="table-main">
+			<el-table
+				:data="colSetting"
+				row-key="prop"
+				default-expand-all
+        border
+				:tree-props="{ children: '_children'}"
+			>
+				<el-table-column prop="label" align="center" label="列名" />
+				<el-table-column v-slot="scope" prop="isShow" align="center" label="显示">
+					<el-checkbox v-model="scope.row.isShow" @change="update"></el-checkbox>
+				</el-table-column>
+				<template #empty>
+					<Empty />
+				</template>
+			</el-table>
+		</div>
+	</el-drawer>
 </template>
 
 <script setup lang="ts" name="ColSetting">
-import { ref } from "vue";
-import { ColumnProps } from "@/components/ProTable/interface";
-import Empty from "@/components/Empty/index.vue";
-
-defineProps<{ colSetting: ColumnProps[] }>();
+import { ref } from 'vue';
+import { ColumnProps } from '@/components/ProTable/interface';
 
+const props = defineProps<{ colSetting: ColumnProps[] }>();
 const drawerVisible = ref<boolean>(false);
+const emit = defineEmits(['update:colSetting']);
+const update = () => {
+  emit('update:colSetting', props.colSetting);
+};
 
 const openColSetting = () => {
-  drawerVisible.value = true;
+	drawerVisible.value = true;
 };
 
 defineExpose({
-  openColSetting
+	openColSetting,
 });
 </script>
 
 <style scoped lang="scss">
 .cursor-move {
-  cursor: move;
+	cursor: move;
 }
 </style>

+ 106 - 22
src/components/ProTable/components/Pagination.vue

@@ -1,28 +1,112 @@
 <template>
-  <!-- 分页组件 -->
-  <el-pagination
-    :current-page="pageable.pageNum"
-    :page-size="pageable.pageSize"
-    :page-sizes="[10, 25, 50, 100]"
-    :total="pageable.total"
-    layout="total, sizes, prev, pager, next, jumper"
-    @size-change="handleSizeChange"
-    @current-change="handleCurrentChange"
-  ></el-pagination>
+	<div :class="{ hidden: hidden }" class="pagination-container">
+		<el-pagination
+			:background="background"
+			v-model:current-page="currentPage"
+			v-model:page-size="pageSize"
+			:layout="layout"
+			:page-sizes="pageSizes"
+			:pager-count="pagerCount"
+			:total="total"
+			:small="small"
+			@size-change="handleSizeChange"
+			@current-change="handleCurrentChange"
+		/>
+	</div>
 </template>
 
-<script setup lang="ts" name="Pagination">
-interface Pageable {
-  pageNum: number;
-  pageSize: number;
-  total: number;
-}
+<script lang="ts" name="pagination" setup>
+import { computed, onMounted, ref } from 'vue';
+// 定义父组件传过来的值
+const props = defineProps({
+	total: {
+		required: true,
+		type: Number,
+	},
+	page: {
+		type: Number,
+		default: 1,
+	},
+	limit: {
+		type: Number,
+		default: 10,
+	},
+	pageSizes: {
+		type: Array,
+		default() {
+			return [10, 20, 30, 50, 100];
+		},
+	},
+	// 移动端页码按钮的数量端默认值5
+	pagerCount: {
+		type: Number,
+		default: document.body.clientWidth < 992 ? 5 : 7,
+	},
+	layout: {
+		type: String,
+		default: 'total, sizes, prev, pager, next, jumper',
+	},
+	background: {
+		type: Boolean,
+		default: false,
+	},
+	autoScroll: {
+		type: Boolean,
+		default: true,
+	},
+	hidden: {
+		type: Boolean,
+		default: false,
+	},
+});
 
-interface PaginationProps {
-  pageable: Pageable;
-  handleSizeChange: (size: number) => void;
-  handleCurrentChange: (currentPage: number) => void;
-}
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
 
-defineProps<PaginationProps>();
+// 定义变量内容
+const small = ref(false);
+const currentPage = computed({
+	get() {
+		return props.page;
+	},
+	set(val) {
+		emit('update:page', val);
+	},
+});
+const pageSize = computed({
+	get() {
+		return props.limit;
+	},
+	set(val) {
+		emit('update:limit', val);
+	},
+});
+const handleSizeChange = (val: any) => {
+	/**
+	 * 场景:API返回总数为25条,按照10条每页,一共有3页。选了2的页数之后,然后把size选择成30条,
+	 * 这个时候,分页就会同时触发size选择函数以及current选择函数。{page: 2, size: 30},{page: 1, size: 30}请求两次,会导致列表会有暂无数据的情况
+	 * 解决办法:用setTimeout(函数,0),用延迟,虽然还是两次请求,但是每次都是{page: 1, size: 30}
+	 */
+	setTimeout(() => {
+		emit('pagination', { page: currentPage.value, limit: val });
+	}, 0);
+};
+const handleCurrentChange = (val: any) => {
+	emit('pagination', { page: val, limit: pageSize.value });
+};
+onMounted(() => {
+	// 监听布局大小 改变分页的大小
+	// let themeConfig = Local.get('themeConfig');
+	// small.value = themeConfig.globalComponentSize == 'small';
+});
 </script>
+<style scoped>
+.pagination-container {
+	padding-top: 20px;
+	display: flex;
+	justify-content: flex-end;
+}
+.pagination-container.hidden {
+	display: none;
+}
+</style>

+ 220 - 194
src/components/ProTable/index.vue

@@ -1,263 +1,289 @@
 <!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
-
 <template>
-  <!-- 表格主体 -->
-  <div class="card table-main">
-    <!-- 表格头部 操作按钮 -->
-    <div class="table-header">
-      <div class="header-button-lf">
-        <slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" />
-      </div>
-      <div v-if="toolButton" class="header-button-ri">
-        <slot name="toolButton">
-          <el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="getTableList" />
-          <el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" />
-        </slot>
-      </div>
-    </div>
-    <!-- 表格主体 -->
-    <el-table
-      ref="tableRef"
-      v-bind="$attrs"
-      :data="processTableData"
-      :border="border"
-      :row-key="rowKey"
-      @selection-change="selectionChange"
-    >
-      <!-- 默认插槽 -->
-      <slot />
-      <template v-for="item in tableColumns" :key="item">
-        <!-- selection || radio || index || expand || sort -->
-        <el-table-column
-          v-if="item.type && columnTypes.includes(item.type)"
-          v-bind="item"
-          :align="item.align ?? 'center'"
-          :reserve-selection="item.type == 'selection'"
-        >
-          <template #default="scope">
-            <!-- expand -->
-            <template v-if="item.type == 'expand'">
-              <component :is="item.render" v-bind="scope" v-if="item.render" />
-              <slot v-else :name="item.type" v-bind="scope" />
-            </template>
-            <!-- radio -->
-            <el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]">
-              <i></i>
-            </el-radio>
-            <!-- sort -->
-            <el-tag v-if="item.type == 'sort'" class="move">
-              <el-icon> <DCaret /></el-icon>
-            </el-tag>
-          </template>
-        </el-table-column>
-        <!-- other -->
-        <TableColumn v-if="!item.type && item.prop && item.isShow" :column="item" col-setting="">
-          <template v-for="slot in Object.keys($slots)" #[slot]="scope">
-            <slot :name="slot" v-bind="scope" />
-          </template>
-        </TableColumn>
-      </template>
-      <!-- 插入表格最后一行之后的插槽 -->
-      <template #append>
-        <slot name="append" />
-      </template>
-      <!-- 无数据 -->
-      <template #empty>
-        <Empty />
-      </template>
-    </el-table>
-    <!-- 分页组件 -->
-    <slot name="pagination">
-      <Pagination
-        v-if="pagination"
-        :pageable="pageable"
-        :handle-size-change="handleSizeChange"
-        :handle-current-change="handleCurrentChange"
-      />
-    </slot>
-  </div>
-  <!-- 列设置 -->
-  <ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" />
+	<!-- 表格主体 -->
+	<div class="card table-main">
+		<!-- 表格头部 操作按钮 -->
+		<div class="table-header">
+			<div class="header-button-lf">
+				<slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" />
+			</div>
+			<div v-if="toolButton" class="header-button-ri">
+				<slot name="toolButton">
+					<el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="onRefresh" title="刷新表格" />
+					<el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" title="列设置" />
+				</slot>
+			</div>
+		</div>
+		<!-- 表格主体 -->
+		<el-table ref="tableRef" v-bind="$attrs" :data="processTableData" :border="border" :row-key="rowKey" @selection-change="selectionChange" v-loading="loading">
+			<!-- 默认插槽 -->
+			<slot />
+			<template v-for="item in tableColumns" :key="item">
+				<!-- selection || radio || index || expand || sort -->
+				<el-table-column
+					v-if="item.type && columnTypes.includes(item.type)"
+					v-bind="item"
+					:align="item.align ?? 'center'"
+					:reserve-selection="item.type == 'selection'"
+				>
+					<template #default="scope">
+						<!-- expand -->
+						<template v-if="item.type == 'expand'">
+							<component :is="item.render" v-bind="scope" v-if="item.render" />
+							<slot v-else :name="item.type" v-bind="scope" />
+						</template>
+						<!-- radio -->
+						<el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]">
+							<i></i>
+						</el-radio>
+						<!-- sort -->
+						<el-tag v-if="item.type == 'sort'" class="move">
+							<SvgIcon name="ele-Rank" />
+						</el-tag>
+					</template>
+				</el-table-column>
+				<!-- other -->
+				<TableColumn v-if="!item.type && item.prop && item.isShow" :column="item" col-setting="">
+					<template v-for="slot in Object.keys($slots)" #[slot]="scope">
+						<slot :name="slot" v-bind="scope" />
+					</template>
+				</TableColumn>
+			</template>
+			<!-- 插入表格最后一行之后的插槽 -->
+			<template #append>
+				<slot name="append" />
+			</template>
+			<!-- 无数据 -->
+			<template #empty>
+				<Empty />
+			</template>
+		</el-table>
+		<!-- 分页组件 -->
+		<slot name="pagination">
+			<Pagination v-if="pagination" @pagination="pageChange" :total="total" />
+		</slot>
+	</div>
+	<!-- 列设置 -->
+	<ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" @update:colSetting="updateColSetting" />
 </template>
 
 <script setup lang="ts" name="ProTable">
-import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue";
-import { ElTable } from "element-plus";
-import { useTable } from "@/hooks/useTable";
-import { useSelection } from "@/hooks/useSelection";
-import { ColumnProps, TypeProps } from "@/components/ProTable/interface";
-import { Refresh, Operation } from "@element-plus/icons-vue";
-import Pagination from "./components/Pagination.vue";
-import ColSetting from "./components/ColSetting.vue";
-import TableColumn from "./components/TableColumn.vue";
-import Sortable from "sortablejs";
-import Empty from "@/components/Empty/index.vue";
-
-export interface ProTableProps {
-  columns: ColumnProps[]; // 列配置项  ==> 必传
-  data?: any[]; // 静态 table data 数据,若存在则不会使用 requestApi 返回的 data ==> 非必传
-  requestApi?: (params: any) => Promise<any>; // 请求表格数据的 api ==> 非必传
-  requestAuto?: boolean; // 是否自动执行请求 api ==> 非必传(默认为true)
-  requestError?: (params: any) => void; // 表格 api 请求错误监听 ==> 非必传
-  dataCallback?: (data: any) => any; // 返回数据的回调函数,可以对数据进行处理 ==> 非必传
-  title?: string; // 表格标题 ==> 非必传
-  pagination?: boolean; // 是否需要分页组件 ==> 非必传(默认为true)
-  initParam?: any; // 初始化请求参数 ==> 非必传(默认为{})
-  border?: boolean; // 是否带有纵向边框 ==> 非必传(默认为true)
-  toolButton?: ("refresh" | "setting" | "search")[] | boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true)
-  rowKey?: string; // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
-}
+import { ref, watch, provide, onMounted, unref, computed, reactive, PropType } from 'vue';
+import { ElTable } from 'element-plus';
+import { useTable } from '@/hooks/useTable';
+import { useSelection } from '@/hooks/useSelection';
+import { ColumnProps, TypeProps } from '@/components/ProTable/interface';
+import { Refresh, Operation } from '@element-plus/icons-vue';
+import Pagination from './components/Pagination.vue';
+import ColSetting from './components/ColSetting.vue';
+import TableColumn from './components/TableColumn.vue';
+import Sortable from 'sortablejs';
+import { handleProp } from '@/utils/tools';
 
 // 接受父组件参数,配置默认值
-const props = withDefaults(defineProps<ProTableProps>(), {
-  columns: () => [],
-  requestAuto: true,
-  pagination: true,
-  initParam: {},
-  border: true,
-  toolButton: true,
-  rowKey: "id",
+const props = defineProps({
+	columns: {
+		// 列配置项  ==> 必传
+		type: Array as PropType<ColumnProps[]>,
+		required: true,
+	},
+	data: {
+		// 静态 table data 数据
+		type: Array,
+		default: () => [],
+	},
+	pagination: {
+		// 是否需要分页组件 ==> 非必传(默认为true)
+		type: Boolean,
+		default: true,
+	},
+	total: {
+		// 总条数 ==> 非必传(默认为0)
+		type: Number,
+		default: 0,
+	},
+	border: {
+		// 是否带有纵向边框 ==> 非必传(默认为false)
+		type: Boolean,
+		default: false,
+	},
+	toolButton: {
+		// 是否显示表格功能按钮 ==> 非必传(默认为true)
+		type: [Array, Boolean],
+		default: true,
+	},
+	rowKey: {
+		// 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
+		type: String,
+		default: 'id',
+	},
+  loading: {
+    // 是否显示加载中
+    type: Boolean,
+    default: false,
+  },
 });
 
 // table 实例
 const tableRef = ref<InstanceType<typeof ElTable>>();
 
 // column 列类型
-const columnTypes: TypeProps[] = ["selection", "radio", "index", "expand", "sort"];
-
+const columnTypes: TypeProps[] = ['selection', 'radio', 'index', 'expand', 'sort'];
 
 // 控制 ToolButton 显示
-const showToolButton = (key: "refresh" | "setting" | "search") => {
-  return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
+const showToolButton = (key: 'refresh' | 'setting') => {
+	return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
 };
 
 // 单选值
-const radio = ref("");
+const radio = ref('');
 
 // 表格多选 Hooks
 const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey);
 
 // 表格操作 Hooks
-const { tableData, pageable, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } =
-  useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError);
+const { tableData, pageable, searchInitParam, searchParam, getTableList, reset } = useTable();
 
 // 清空选中数据列表
 const clearSelection = () => tableRef.value!.clearSelection();
 
 // 初始化表格数据 && 拖拽排序
 onMounted(() => {
-  dragSort();
-  props.requestAuto && getTableList();
-  props.data && (pageable.value.total = props.data.length);
+	dragSort();
+	props.data && (pageable.value.total = props.data.length);
 });
 
 // 处理表格数据
 const processTableData = computed(() => {
-  if (!props.data) return tableData.value;
-  if (!props.pagination) return props.data;
-  return props.data.slice(
-    (pageable.value.pageNum - 1) * pageable.value.pageSize,
-    pageable.value.pageSize * pageable.value.pageNum
-  );
+	if (!props.data) return tableData.value;
+	if (!props.pagination) return props.data;
+	return props.data.slice((pageable.value.pageNum - 1) * pageable.value.pageSize, pageable.value.pageSize * pageable.value.pageNum);
 });
 
-// 监听页面 initParam 改化,重新获取表格数据
-watch(() => props.initParam, getTableList, { deep: true });
-
 // 接收 columns 并设置为响应式
 const tableColumns = reactive<ColumnProps[]>(props.columns);
 
 // 扁平化 columns
 const flatColumns = computed(() => flatColumnsFunc(tableColumns));
-
 // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择)
 const enumMap = ref(new Map<string, { [key: string]: any }[]>());
 const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
-  if (!enumValue) return;
+	if (!enumValue) return;
 
-  // 如果当前 enumMap 存在相同的值 return
-  if (enumMap.value.has(prop!) && (typeof enumValue === "function" || enumMap.value.get(prop!) === enumValue)) return;
+	// 如果当前 enumMap 存在相同的值 return
+	if (enumMap.value.has(prop!) && (typeof enumValue === 'function' || enumMap.value.get(prop!) === enumValue)) return;
 
-  // 当前 enum 为静态数据,则直接存储到 enumMap
-  if (typeof enumValue !== "function") return enumMap.value.set(prop!, unref(enumValue!));
+	// 当前 enum 为静态数据,则直接存储到 enumMap
+	if (typeof enumValue !== 'function') return enumMap.value.set(prop!, unref(enumValue!));
 
-  // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
-  enumMap.value.set(prop!, []);
+	// 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
+	enumMap.value.set(prop!, []);
 
-  // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
-  const { data } = await enumValue();
-  enumMap.value.set(prop!, data);
+	// 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
+	const { data } = await enumValue();
+	enumMap.value.set(prop!, data);
 };
 
 // 注入 enumMap
-provide("enumMap", enumMap);
+provide('enumMap', enumMap);
 
 // 扁平化 columns 的方法
 const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
-  columns.forEach(async col => {
-    if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
-    flatArr.push(col);
-
-    // column 添加默认 isShow && isFilterEnum 属性值
-    col.isShow = col.isShow ?? true;
-    col.isFilterEnum = col.isFilterEnum ?? true;
-
-    // 设置 enumMap
-    await setEnumMap(col);
-  });
-  return flatArr.filter(item => !item._children?.length);
+	columns.forEach(async (col) => {
+		if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
+		flatArr.push(col);
+
+		// column 添加默认 isShow && isFilterEnum 属性值
+		col.isShow = col.isShow ?? true;
+		col.isFilterEnum = col.isFilterEnum ?? true;
+
+		// 设置 enumMap
+		await setEnumMap(col);
+	});
+	return flatArr.filter((item) => !item._children?.length);
 };
 
+// 过滤需要搜索的配置项 && 排序
+const searchColumns = computed(() => {
+	return flatColumns.value?.filter((item) => item.search?.el || item.search?.render).sort((a, b) => a.search!.order! - b.search!.order!);
+});
 
+// 设置 搜索表单默认排序 && 搜索表单项的默认值
+searchColumns.value?.forEach((column, index) => {
+	column.search!.order = column.search?.order ?? index + 2;
+	const key = column.search?.key ?? handleProp(column.prop!);
+	const defaultValue = column.search?.defaultValue;
+	if (defaultValue !== undefined && defaultValue !== null) {
+		searchInitParam.value[key] = defaultValue;
+		searchParam.value[key] = defaultValue;
+	}
+});
 
 // 列设置 ==> 需要过滤掉不需要设置的列
 const colRef = ref();
-const colSetting = tableColumns!.filter(item => {
-  const { type, prop, isShow } = item;
-  return !columnTypes.includes(type!) && prop !== "operation" && isShow;
+const colSetting = tableColumns!.filter((item) => {
+	const { type, prop, isShow } = item;
+	return !columnTypes.includes(type!) && prop !== 'operation' && isShow;
 });
 const openColSetting = () => colRef.value.openColSetting();
 
-// 定义 emit 事件
-const emit = defineEmits<{
-  search: [];
-  reset: [];
-  dargSort: [{ newIndex?: number; oldIndex?: number }];
-}>();
-
-
-
-
 // 拖拽排序
 const dragSort = () => {
-  const tbody = document.querySelector(".el-table__body-wrapper tbody") as HTMLElement;
-  Sortable.create(tbody, {
-    handle: ".move",
-    animation: 300,
-    onEnd({ newIndex, oldIndex }) {
-      const [removedItem] = processTableData.value.splice(oldIndex!, 1);
-      processTableData.value.splice(newIndex!, 0, removedItem);
-      emit("dargSort", { newIndex, oldIndex });
-    }
-  });
+	const tbody = document.querySelector('.el-table__body-wrapper tbody') as HTMLElement;
+	Sortable.create(tbody, {
+		handle: '.move',
+		animation: 300,
+		onEnd({ newIndex, oldIndex }) {
+			const [removedItem] = processTableData.value.splice(oldIndex!, 1);
+			processTableData.value.splice(newIndex!, 0, removedItem);
+			emit('dargSort', { newIndex, oldIndex });
+		},
+	});
+};
+const emit = defineEmits(['dargSort', 'updateColSetting', 'selectChange', 'updateTable']);
+// 刷新事件
+const onRefresh = () => {
+	emit('updateTable');
+};
+// 分页事件
+const pageChange = (val: any) => {
+	getTableList();
+	emit('updateTable', val);
+};
+// 更新列设置
+const updateColSetting = (colSetting: ColumnProps[]) => {
+	//  平铺数组后获取isShow为true的对象
+	const flatColumns = flatColumnsFunc(colSetting);
+	const newColumns = flatColumns.filter((item) => item.isShow);
+	// 获取prop
+	const newColumnsProp = newColumns.map((item) => item.prop);
+	emit('updateColSetting', newColumns, newColumnsProp);
 };
-
 // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去)
 defineExpose({
-  element: tableRef,
-  tableData: processTableData,
-  radio,
-  pageable,
-  searchInitParam,
-  getTableList,
-  search,
-  reset,
-  handleSizeChange,
-  handleCurrentChange,
-  clearSelection,
-  enumMap,
-  isSelected,
-  selectedList,
-  selectedListIds
+	element: tableRef,
+	tableData: processTableData,
+	radio,
+	pageable,
+	searchInitParam,
+	getTableList,
+	reset,
+	clearSelection,
+	enumMap,
+	isSelected,
+	selectedList,
+	selectedListIds,
 });
 </script>
+<style scoped lang="scss">
+.table-header {
+	margin-bottom: 20px;
+	display: flex;
+	justify-content: space-between;
+}
+.header-button-lf {
+	flex: 1;
+}
+.header-button-ri {
+}
+</style>

+ 0 - 3
src/hooks/useTable.ts

@@ -1,5 +1,4 @@
 import { reactive, computed, toRefs } from "vue";
-
 /**
  * @description table 页面操作方法封装
  * @param {Function} api 获取表格数据 api 方法 (必传)
@@ -29,8 +28,6 @@ export const useTable = (
     },
     // 查询参数(只包括查询)
     searchParam: {},
-    // 初始化默认的查询参数
-    searchInitParam: {},
     // 总参数(包含分页和查询参数)
     totalParam: {}
   });

+ 3 - 1
src/stores/themeConfig.ts

@@ -138,7 +138,9 @@ export const useThemeConfig = defineStore('themeConfig', {
 			// 默认全局组件大小,可选值"<large|'default'|small>",默认 'default'
 			globalComponentSize: 'default',
 			// 登录页面背景图
-			loginImage: `url(${getImageUrl('login/bg.png')}`
+			loginImage: `url(${getImageUrl('login/bg.png')}`,
+			// 登录页是否展示短信验证码
+			isLoginMessageCode: false,
 		},
 	}),
 	actions: {

+ 1 - 0
src/types/pinia.d.ts

@@ -93,6 +93,7 @@ declare interface ThemeConfigState {
 	globalI18n: string;
 	globalComponentSize: string;
 	loginImage: string;
+	isLoginMessageCode: boolean;
 }
 declare interface ThemeConfigStates {
 	themeConfig: ThemeConfigState;

+ 4 - 4
src/utils/tools.ts

@@ -257,8 +257,8 @@ export function isArray(val: any): val is Array<any> {
  * */
 export function formatValue(callValue: any) {
 	// 如果当前值为数组,使用 / 拼接(根据需求自定义)
-	if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : "--";
-	return callValue ?? "--";
+	if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : "";
+	return callValue ?? "";
 }
 
 /**
@@ -268,7 +268,7 @@ export function formatValue(callValue: any) {
  * @returns {*}
  * */
 export function handleRowAccordingToProp(row: { [key: string]: any }, prop: string) {
-	if (!prop.includes(".")) return row[prop] ?? "--";
-	prop.split(".").forEach(item => (row = row[item] ?? "--"));
+	if (!prop.includes(".")) return row[prop] ?? "";
+	prop.split(".").forEach(item => (row = row[item] ?? ""));
 	return row;
 }

+ 3 - 2
src/views/business/citizen/components/Tags-edit.vue

@@ -16,7 +16,7 @@
 							</p>
 							<p class="form-item">
 								<span class="form-label">首次联系:</span>
-								<span class="flex-1">{{ formatDate(formData.citizen.oneCallTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+								<span class="flex-1">{{ formatDate(formData.oneCallTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
 							</p>
 							<p class="form-item">
 								<span class="form-label">上次联系:</span>
@@ -124,6 +124,7 @@ const state = reactive<any>({
 });
 const formData = reactive({
 	lastCallTime: '', // 上次联系时间
+  oneCallTime: '', // 首次联系时间
 	hotspotNames: [], // 关注诉求
 	order: {
 		allOrderNum: 0, // 全部工单
@@ -138,7 +139,6 @@ const formData = reactive({
 	},
 	citizen: {
 		name: '', // 姓名
-    oneCallTime: '', // 首次联系时间
 		id: '', // 市民id
 	},
 	label: [], // 市民标签
@@ -161,6 +161,7 @@ const getDetail = async (phone: string) => {
 	try {
 		const { result } = await citizenDetailByPhone(phone);
 		formData.lastCallTime = result.lastCallTime; // 上次联系时间
+    formData.oneCallTime = result.oneCallTime; // 首次联系时间
 		formData.hotspotNames = result.hotspotNames ? result.hotspotNames.split(',') : []; // 关注诉求
 		formData.order = result.order; // 工单历史
 		formData.callHistory = result.callHistory; // 来电历史

+ 89 - 62
src/views/login/component/Account.vue

@@ -37,7 +37,7 @@
 		<motion :delay="300">
 			<el-form-item
 				prop="verifyCode"
-        class="mb30"
+				class="mb30"
 				:rules="[
 					{ required: true, message: '请输入验证码', trigger: 'blur' },
 					{
@@ -67,39 +67,37 @@
 				</el-col>
 			</el-form-item>
 		</motion>
-<!--    <motion :delay="400">
-    <el-form-item prop="code" class="mb30" :rules="[{ required: false, message: '请输入短信验证码', trigger: 'blur' }]">
-      <el-col :span="15">
-        <el-input
-          type="text"
-          maxlength="4"
-          placeholder="请输入短信验证码"
-          v-model="state.ruleForm.code"
-          clearable
-          autocomplete="off"
-          class="inputDeep"
-        >
-          <template #prefix>
-            <SvgIcon name="ele-ChatDotSquare" class="el-input__icon" />
-          </template>
-        </el-input>
-      </el-col>
-      <el-col :span="1"></el-col>
-      <el-col :span="8">
-        <el-button class="login-content-code" :disabled="isDisabled" @click="getIdentifyCodeBtn">{{
-            isDisabled ? count + 's后重新获取' : click
-          }}</el-button>
-      </el-col>
-    </el-form-item>
-  </motion>-->
+		<motion :delay="400" v-if="themeConfig.isLoginMessageCode">
+			<el-form-item prop="msgCode" class="mb30" :rules="[{ required: msgCodeRequired, message: '请输入短信验证码', trigger: 'blur' }]">
+				<el-col :span="11">
+					<el-input
+						type="text"
+						maxlength="6"
+						placeholder="请输入短信验证码"
+						v-model="state.ruleForm.msgCode"
+						clearable
+						autocomplete="off"
+						class="inputDeep"
+						@keyup.enter="onSignIn(ruleFormRef)"
+					>
+						<template #prefix>
+							<SvgIcon name="ele-ChatDotSquare" class="el-input__icon" />
+						</template>
+					</el-input>
+				</el-col>
+				<el-col :span="12" :offset="1">
+					<el-button class="login-content-code w100" :disabled="isDisabled" @click="getIdentifyCodeBtn">{{
+						isDisabled ? countText : click
+					}}</el-button>
+				</el-col>
+			</el-form-item>
+		</motion>
 		<motion :delay="400">
 			<el-form-item>
 				<el-button type="primary" class="login-content-submit" round @click="onSignIn(ruleFormRef)" :loading="state.loading">登录</el-button>
 			</el-form-item>
 		</motion>
-    <motion :delay="500">
-        运营管理系统 <span class="color-danger font-bold">v5.0</span>
-    </motion>
+		<motion :delay="500"> 运营管理系统 <span class="color-danger font-bold">v5.0</span> </motion>
 		<motion :delay="500">
 			<div class="login-msg">
 				<div>联系管理员<b>重置密码</b></div>
@@ -112,7 +110,7 @@
 <script setup lang="ts" name="loginAccount">
 import { reactive, computed, ref, defineAsyncComponent } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
-import { ElNotification } from 'element-plus';
+import { ElMessage, ElNotification } from 'element-plus';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '@/stores/themeConfig';
 import { initFrontEndControlRoutes } from '@/router/frontEnd';
@@ -121,11 +119,10 @@ import { Session, Local, Cookie } from '@/utils/storage';
 import { formatAxis } from '@/utils/formatTime';
 import { NextLoading } from '@/utils/loading';
 import type { FormInstance } from 'element-plus';
-import { signIn } from '@/api/login';
+import { sendCode, signIn } from '@/api/login';
 import { JSEncrypt } from 'jsencrypt'; // rsa加密
 import { throttle } from '@/utils/tools';
 import Motion from '@/utils/motion';
-import { verifyPhone } from "@/utils/toolsValidate";
 
 const ReImageVerify = defineAsyncComponent(() => import('@/components/ImgVerify/index.vue'));
 
@@ -139,6 +136,7 @@ const state = reactive<any>({
 		username: '', // 账号
 		password: '', // 密码
 		verifyCode: '', // 验证码
+		msgCode: '', // 短信验证码
 	},
 	loading: false, // 加载
 });
@@ -161,36 +159,64 @@ const validatePass = (rule: any, value: any, callback: any) => {
 };
 // 验证码
 const verifyCode = ref<string>(''); // 验证码
-const count = ref(60);
-const click = ref('获取验证码');
-const isDisabled = ref(false);
+const count = ref(300); // 倒计时
+const countText = ref('s后重新获取'); // 倒计时文本
+const click = ref('获取验证码'); // 点击
+const isDisabled = ref(false); // 是否禁用
+const msgCodeRequired = ref(true); // 短信验证码是否必填
 const getIdentifyCodeBtn = () => {
-  if(!state.ruleForm.userName) {
-    ruleFormRef.value?.validateField('userName');
-    return;
-  }
-  if (!verifyPhone(state.ruleForm.userName)) {
-    ruleFormRef.value?.validateField('userName');
-    return;
-  }
-  // state.loading = true;
-  countDown();
+	if (!state.ruleForm.username) {
+		ruleFormRef.value?.validateField('username');
+		return;
+	}
+	if (!state.ruleForm.verifyCode) {
+		ruleFormRef.value?.validateField('verifyCode');
+		return;
+	}
+	// 验证码错误
+	if (state.ruleForm.verifyCode.toUpperCase() !== verifyCode.value.toUpperCase()) {
+		ruleFormRef.value?.validateField('verifyCode');
+		return;
+	}
+	// state.loading = true;
+	// 获取短信验证码
+	sendCode(state.ruleForm.username)
+		.then((res: any) => {
+			if (res.result != '验证码发送成功') {
+				if (res.result === '账号不受限,请直接登录。') msgCodeRequired.value = false;
+				countText.value = res.result;
+				ElNotification({
+					title: '提示',
+					message: res.result,
+					type: 'info',
+				});
+				isDisabled.value = true;
+			} else {
+				countDown();
+				ElMessage.success('短信验证码已发送,请注意查收');
+			}
+		})
+		.catch(() => {
+			// state.loading = false;
+		});
 };
 // 倒计时
 const countDown = () => {
-  if (count.value === 0) {
-    isDisabled.value = false;
-    click.value = '获取验证码';
-    count.value = 60;
-    return;
-  } else {
-    count.value--;
-    click.value = count.value + 's后重新获取';
-    isDisabled.value = true;
-    setTimeout(() => {
-      countDown();
-    }, 1000);
-  }
+	if (count.value === 0) {
+		isDisabled.value = false;
+		click.value = '获取验证码';
+		count.value = 300;
+		countText.value = `${count.value}s后重新获取`;
+		return;
+	} else {
+		count.value--;
+		click.value = count.value + 's后重新获取';
+		countText.value = `${count.value}s后重新获取`;
+		isDisabled.value = true;
+		setTimeout(() => {
+			countDown();
+		}, 1000);
+	}
 };
 // 登录
 const onSignIn = throttle(async (formEl: FormInstance | undefined) => {
@@ -208,6 +234,7 @@ const onSignIn = throttle(async (formEl: FormInstance | undefined) => {
 		const submitObj = {
 			username: encryptor.encrypt(state.ruleForm.username),
 			password: encryptor.encrypt(state.ruleForm.password),
+			msgCode: encryptor.encrypt(state.ruleForm.msgCode),
 		};
 		signIn(submitObj)
 			.then(async (res: any) => {
@@ -287,19 +314,19 @@ const signInSuccess = (isNoPower: boolean | undefined) => {
 
 .login-content-form {
 	margin-top: 20px;
-  font-size: var(--el-font-size-medium);
+	font-size: var(--el-font-size-medium);
 
 	:deep(.el-input--large) {
-    font-size: var(--el-font-size-medium);
+		font-size: var(--el-font-size-medium);
 	}
 
 	:deep(.el-form-item__error) {
-    font-size: var(--el-font-size-medium);
+		font-size: var(--el-font-size-medium);
 	}
 
 	.login-content-submit {
 		width: 100%;
-		margin-top: 45px;
+		margin-top: 25px;
 		height: 50px;
 		border-radius: 30px;
 		font-size: 16px;

+ 145 - 124
src/views/statistics/order/departmentAcceptType.vue

@@ -1,95 +1,56 @@
 <template>
-  <div class="statistics-order-department-accept-type-container layout-pd">
-    <!-- 搜索  -->
-    <el-card shadow="never">
-      <el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent  inline>
-        <el-form-item label="归档类型" prop="TypeCode">
-          <el-select v-model="state.queryParams.TypeCode" placeholder="部门名称" @change="queryList">
-            <el-option label="全部" value="0" />
-            <el-option label="中心归档" value="1" />
-            <el-option label="部门归档" value="2" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="时间段" prop="crTime">
-          <el-date-picker
-            v-model="state.queryParams.crTime"
-            type="datetimerange"
-            unlink-panels
-            range-separator="至"
-            start-placeholder="开始时间"
-            end-placeholder="结束时间"
-            :shortcuts="shortcuts"
-            @change="queryList"
-            value-format="YYYY-MM-DD[T]HH:mm:ss"
-          />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
-          <el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
-            <SvgIcon name="ele-Refresh" class="mr5" />重置
-          </el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-    <el-card shadow="never">
-      <!-- 表格 -->
-      <el-table
-        :data="state.tableData"
-        v-loading="state.loading"
+	<div class="statistics-order-department-accept-type-container layout-pd">
+		<!-- 搜索  -->
+		<el-card shadow="never">
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent inline>
+				<el-form-item label="归档类型" prop="TypeCode">
+					<el-select v-model="state.queryParams.TypeCode" placeholder="部门名称" @change="queryList">
+						<el-option label="全部" value="0" />
+						<el-option label="中心归档" value="1" />
+						<el-option label="部门归档" value="2" />
+					</el-select>
+				</el-form-item>
+				<el-form-item label="时间段" prop="crTime">
+					<el-date-picker
+						v-model="state.queryParams.crTime"
+						type="datetimerange"
+						unlink-panels
+						range-separator="至"
+						start-placeholder="开始时间"
+						end-placeholder="结束时间"
+						:shortcuts="shortcuts"
+						@change="queryList"
+						value-format="YYYY-MM-DD[T]HH:mm:ss"
+					/>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
+						<SvgIcon name="ele-Refresh" class="mr5" />重置
+					</el-button>
+				</el-form-item>
+			</el-form>
+		</el-card>
+		<el-card shadow="never">
+			<!-- 表格 -->
+			<ProTable
+				ref="proTableRef"
+				:pagination="false"
+				:columns="columns"
+				:data="state.tableData"
+				@updateTable="queryList"
+				:toolButton="false"
+				:loading="state.loading"
         show-summary
-      >
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column prop="orgName" label="部门名称" show-overflow-tooltip align="center" min-width="120"></el-table-column>
-        <el-table-column prop="orgType" label="部门类别" show-overflow-tooltip align="center" min-width="120"></el-table-column>
-        <el-table-column label="所有类型" align="center">
-          <el-table-column label="总件数" show-overflow-tooltip align="center" prop="allCount">
-            <template #default="{ row }">
-              <el-button link @click="handleDetail(row)" type="primary">{{ row.allCount }}</el-button>
-            </template>
-          </el-table-column>
-          <el-table-column prop="allTimes" label="总时长" show-overflow-tooltip align="center"></el-table-column>
-          <el-table-column prop="averageTime" label="总平均" show-overflow-tooltip align="center"></el-table-column>
-        </el-table-column>
-        <el-table-column label="咨询类" align="center">
-          <el-table-column prop="zxAllCount" label="咨询件数" show-overflow-tooltip align="center" min-width="90px">
-            <template #default="{ row }">
-              <el-button link @click="handleDetail(row)" type="primary">{{ row.zxAllCount }}</el-button>
-            </template>
-          </el-table-column>
-          <el-table-column prop="zxAllTimes" label="咨询时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="zxAverageTime" label="咨询平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="举报类" align="center">
-          <el-table-column prop="jbAllCount" label="举报件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jbAllTimes" label="举报时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jbAverageTime" label="举报平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="投诉类" align="center">
-          <el-table-column prop="tsAllCount" label="投诉件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="tsAllTimes" label="投诉时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="tsAverageTime" label="投诉平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="求助类" align="center">
-          <el-table-column prop="qzAllCount" label="求助件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="qzAllTimes" label="求助时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="qzAverageTime" label="求助平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="建议类" align="center">
-          <el-table-column prop="jyAllCount" label="建议件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jyAllTimes" label="建议时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jyAverageTime" label="建议平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-<!--        <el-table-column label="意见类" align="center">
-          <el-table-column prop="centreArchive" label="意见件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="centreCareOf" label="意见时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="centreCareOf" label="意见平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>-->
-        <template #empty>
-          <Empty />
-        </template>
-      </el-table>
-    </el-card>
-  </div>
+			>
+<!--				<template #allCount="{ row }">
+					<el-button type="primary" link>
+						{{ row.allCount }}
+					</el-button>
+				</template>-->
+			</ProTable>
+		</el-card>
+	</div>
 </template>
 <script setup lang="ts" name="statisticsOrderDepartmentAcceptType">
 import { onMounted, reactive, ref } from 'vue';
@@ -98,57 +59,117 @@ import { throttle } from '@/utils/tools';
 import { departmentAcceptType } from '@/api/statistics/order';
 import { shortcuts } from '@/utils/constants';
 import dayjs from 'dayjs';
+import ProTable from '@/components/ProTable/index.vue';
 // 定义变量内容
 const ruleFormRef = ref<RefType>(); // 表单ref
-const state = reactive(<any>{
-  queryParams: {
-    // 查询条件
-    TypeCode: '0', // 关键词
-    crTime: [dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')], // 时间默认今天开始到今天结束
+const columns = reactive<any[]>([
+	{ type: 'selection', fixed: 'left', width: 55 },
+	{ prop: 'orgName', label: '部门名称',minWidth:100 },
+	{ prop: 'orgType', label: '部门类别',minWidth:100 },
+	{
+		prop: 'allType',
+		label: '所有类型',
+		_children: [
+			{ prop: 'allCount', label: '总件数' },
+			{ prop: 'allTimes', label: '总时长' },
+			{ prop: 'averageTime', label: '总平均' },
+		],
+	},
+  {
+    prop: 'zxType',
+    label: '咨询类',
+    _children: [
+      { prop: 'zxAllCount', label: '咨询件数' },
+      { prop: 'zxAllTimes', label: '咨询时长' },
+      { prop: 'zxAverageTime', label: '咨询平均' },
+    ],
+  },
+  {
+    prop: 'jbType',
+    label: '举报类',
+    _children: [
+      { prop: 'jbAllCount', label: '举报件数' },
+      { prop: 'jbAllTimes', label: '举报时长' },
+      { prop: 'jbAverageTime', label: '举报平均' },
+    ],
+  },
+  {
+    prop: 'tsType',
+    label: '投诉类',
+    _children: [
+      { prop: 'tsAllCount', label: '投诉件数' },
+      { prop: 'tsAllTimes', label: '投诉时长' },
+      { prop: 'tsAverageTime', label: '投诉平均' },
+    ],
   },
-  tableData: [], //表单
-  loading: false, // 加载
-  total: 0, // 总数
+  {
+    prop: 'qzType',
+    label: '求助类',
+    _children: [
+      { prop: 'qzAllCount', label: '求助件数' },
+      { prop: 'qzAllTimes', label: '求助时长' },
+      { prop: 'qzAverageTime', label: '求助平均' },
+    ],
+  },
+  {
+    prop: 'jyType',
+    label: '建议类',
+    _children: [
+      { prop: 'jyAllCount', label: '建议件数' },
+      { prop: 'jyAllTimes', label: '建议时长' },
+      { prop: 'jyAverageTime', label: '建议平均' },
+    ],
+  },
+]);
+const state = reactive(<any>{
+	queryParams: {
+		// 查询条件
+		TypeCode: '0', // 关键词
+		crTime: [dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')], // 时间默认今天开始到今天结束
+	},
+	tableData: [], //表单
+	loading: false, // 加载
+	total: 0, // 总数
 });
 /** 获取列表 */
 const queryList = throttle(() => {
-  state.loading = true;
-  let StartDate = null;
-  let EndDate = null;
-  if (state.queryParams?.crTime) {
-    StartDate = state.queryParams?.crTime[0];
-    EndDate = state.queryParams?.crTime[1];
-  }
-  const request = {
-    StartDate,
-    EndDate,
-    TypeCode:state.queryParams.TypeCode,
-  };
-  departmentAcceptType(request)
-    .then((res: any) => {
-      state.tableData = res.result ?? [];
-      state.total = res.result?.total ?? 0;
-      state.loading = false;
-    })
-    .catch(() => {
-      state.loading = false;
-    });
+	state.loading = true;
+	let StartDate = null;
+	let EndDate = null;
+	if (state.queryParams?.crTime) {
+		StartDate = state.queryParams?.crTime[0];
+		EndDate = state.queryParams?.crTime[1];
+	}
+	const request = {
+		StartDate,
+		EndDate,
+		TypeCode: state.queryParams.TypeCode,
+	};
+	departmentAcceptType(request)
+		.then((res: any) => {
+			state.tableData = res.result ?? [];
+			state.total = res.result?.total ?? 0;
+			state.loading = false;
+		})
+		.catch(() => {
+			state.loading = false;
+		});
 }, 300);
 // 排序
 /** 重置按钮操作 */
 const resetQuery = throttle((formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-  queryList();
+	if (!formEl) return;
+	formEl.resetFields();
+	queryList();
 }, 300);
 // 表格多选
 const multipleTableRef = ref<RefType>();
 const multipleSelection = ref<any>([]);
 const handleSelectionChange = (val: any[]) => {
-  multipleSelection.value = val;
+	multipleSelection.value = val;
 };
 
 onMounted(() => {
-  queryList();
+	queryList();
 });
 </script>

+ 103 - 129
src/views/statistics/order/departmentHandle.vue

@@ -1,150 +1,124 @@
 <template>
-  <div class="statistics-order-department-handle-container layout-pd">
-    <!-- 搜索  -->
-    <el-card shadow="never">
-      <el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent  inline>
-        <el-form-item label="部门名称" prop="Keyword">
-          <el-input v-model="state.queryParams.Keyword" placeholder="部门名称" clearable @keyup.enter="queryList" class="keyword-input" />
-        </el-form-item>
-        <el-form-item label="时间段" prop="crTime">
-          <el-date-picker
-            v-model="state.queryParams.crTime"
-            type="datetimerange"
-            unlink-panels
-            range-separator="至"
-            start-placeholder="开始时间"
-            end-placeholder="结束时间"
-            :shortcuts="shortcuts"
-            @change="queryList"
-            value-format="YYYY-MM-DD[T]HH:mm:ss"
-          />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
-          <el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
-            <SvgIcon name="ele-Refresh" class="mr5" />重置
-          </el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-    <el-card shadow="never">
-      <!-- 表格 -->
-      <el-table
-        :data="state.tableData"
-        v-loading="state.loading"
-        show-summary
-      >
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column prop="orgName" label="部门名称" show-overflow-tooltip align="center" min-width="120"></el-table-column>
-        <el-table-column prop="orgType" label="部门类别" show-overflow-tooltip align="center" min-width="120"></el-table-column>
-        <el-table-column label="所有类型" align="center">
-          <el-table-column label="总件数" show-overflow-tooltip align="center" prop="allCount">
-            <template #default="{ row }">
-              <el-button link @click="handleDetail(row)" type="primary">{{ row.allCount }}</el-button>
-            </template>
-          </el-table-column>
-          <el-table-column prop="allTimes" label="总时长" show-overflow-tooltip align="center"></el-table-column>
-          <el-table-column prop="averageTime" label="总平均" show-overflow-tooltip align="center"></el-table-column>
-        </el-table-column>
-        <el-table-column label="咨询类" align="center">
-          <el-table-column prop="zxAllCount" label="咨询件数" show-overflow-tooltip align="center" min-width="90px">
-            <template #default="{ row }">
-              <el-button link @click="handleDetail(row)" type="primary">{{ row.zxAllCount }}</el-button>
-            </template>
-          </el-table-column>
-          <el-table-column prop="zxAllTimes" label="咨询时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="zxAverageTime" label="咨询平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="举报类" align="center">
-          <el-table-column prop="jbAllCount" label="举报件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jbAllTimes" label="举报时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jbAverageTime" label="举报平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="投诉类" align="center">
-          <el-table-column prop="tsAllCount" label="投诉件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="tsAllTimes" label="投诉时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="tsAverageTime" label="投诉平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="求助类" align="center">
-          <el-table-column prop="qzAllCount" label="求助件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="qzAllTimes" label="求助时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="qzAverageTime" label="求助平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <el-table-column label="建议类" align="center">
-          <el-table-column prop="jyAllCount" label="建议件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jyAllTimes" label="建议时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-          <el-table-column prop="jyAverageTime" label="建议平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-        </el-table-column>
-        <!--        <el-table-column label="意见类" align="center">
-                  <el-table-column prop="centreArchive" label="意见件数" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-                  <el-table-column prop="centreCareOf" label="意见时长" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-                  <el-table-column prop="centreCareOf" label="意见平均" show-overflow-tooltip align="center" min-width="90px"></el-table-column>
-                </el-table-column>-->
-        <template #empty>
-          <Empty />
-        </template>
-      </el-table>
-    </el-card>
-  </div>
+	<div class="statistics-order-department-handle-container layout-pd">
+		<!-- 搜索  -->
+		<el-card shadow="never">
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent inline>
+				<el-form-item label="部门名称" prop="Keyword">
+					<el-input v-model="state.queryParams.Keyword" placeholder="部门名称" clearable @keyup.enter="queryList" class="keyword-input" />
+				</el-form-item>
+				<el-form-item label="时间段" prop="crTime">
+					<el-date-picker
+						v-model="state.queryParams.crTime"
+						type="datetimerange"
+						unlink-panels
+						range-separator="至"
+						start-placeholder="开始时间"
+						end-placeholder="结束时间"
+						:shortcuts="shortcuts"
+						@change="queryList"
+						value-format="YYYY-MM-DD[T]HH:mm:ss"
+					/>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
+						<SvgIcon name="ele-Refresh" class="mr5" />重置
+					</el-button>
+				</el-form-item>
+			</el-form>
+		</el-card>
+		<el-card shadow="never">
+			<ProTable
+				ref="proTableRef"
+				:pagination="false"
+				:columns="columns"
+				:data="state.tableData"
+				@updateTable="queryList"
+				@updateColSetting="updateColSetting"
+        :toolButton="false"
+        :loading="state.loading"
+			>
+			</ProTable>
+		</el-card>
+	</div>
 </template>
-<script setup lang="ts" name="statisticsOrderDepartmentHandle">
+<script setup lang="tsx" name="statisticsOrderDepartmentHandle">
 import { onMounted, reactive, ref } from 'vue';
 import { ElButton, FormInstance } from 'element-plus';
 import { throttle } from '@/utils/tools';
 import { departmentAcceptType } from '@/api/statistics/order';
 import { shortcuts } from '@/utils/constants';
 import dayjs from 'dayjs';
+
+import ProTable from '@/components/ProTable/index.vue'; // 封装表格
+
 // 定义变量内容
 const ruleFormRef = ref<RefType>(); // 表单ref
-const state = reactive(<any>{
-  queryParams: {
-    // 查询条件
-    TypeCode: '0', // 关键词
-    crTime: [dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')], // 时间默认今天开始到今天结束
-  },
-  tableData: [], //表单
-  loading: false, // 加载
-  total: 0, // 总数
+// ProTable 实例
+const proTableRef = ref<RefType>();
+
+// 表格配置项
+const columns = reactive<any[]>([
+	{ type: 'selection', fixed: 'left', width: 55 },
+	{ prop: 'orgName', label: '部门名称' },
+	{ prop: 'orgType', label: '部门类别' },
+	{ prop: 'd', label: '信件总数' },
+	{
+		prop: 'f',
+		label: '办件信息',
+		_children: [
+			{ prop: 'a', label: '已办案件' },
+			{ prop: 'b', label: '在办案件' },
+			{ prop: 'c', label: '办结率' },
+		],
+	},
+]);
+// 列设置获取
+const updateColSetting = (colSetting: any, colArr) => {
+	console.log(colSetting, colArr);
+};
+const state = reactive({
+	queryParams: {
+		// 查询条件
+		TypeCode: '0', // 关键词
+		crTime: [dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')], // 时间默认今天开始到今天结束
+	},
+	tableData: [], //表单
+	loading: false, // 加载
+	total: 0, // 总数
 });
 /** 获取列表 */
 const queryList = throttle(() => {
-  state.loading = true;
-  let StartDate = null;
-  let EndDate = null;
-  if (state.queryParams?.crTime) {
-    StartDate = state.queryParams?.crTime[0];
-    EndDate = state.queryParams?.crTime[1];
-  }
-  const request = {
-    StartDate,
-    EndDate,
-    TypeCode:state.queryParams.TypeCode,
-  };
-  departmentAcceptType(request)
-    .then((res: any) => {
-      state.tableData = res.result ?? [];
-      state.total = res.result?.total ?? 0;
-      state.loading = false;
-    })
-    .catch(() => {
-      state.loading = false;
-    });
+	state.loading = true;
+	let StartDate = null;
+	let EndDate = null;
+	if (state.queryParams?.crTime) {
+		StartDate = state.queryParams?.crTime[0];
+		EndDate = state.queryParams?.crTime[1];
+	}
+	const request = {
+		StartDate,
+		EndDate,
+		TypeCode: state.queryParams.TypeCode,
+	};
+	departmentAcceptType(request)
+		.then((res: any) => {
+			state.tableData = res.result ?? [];
+			state.total = res.result?.total ?? 0;
+			state.loading = false;
+		})
+		.catch(() => {
+			state.loading = false;
+		});
 }, 300);
 // 排序
 /** 重置按钮操作 */
 const resetQuery = throttle((formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-  queryList();
+	if (!formEl) return;
+	formEl.resetFields();
+	queryList();
 }, 300);
-// 表格多选
-const multipleTableRef = ref<RefType>();
-const multipleSelection = ref<any>([]);
-const handleSelectionChange = (val: any[]) => {
-  multipleSelection.value = val;
-};
-
 onMounted(() => {
-  queryList();
+	queryList();
 });
 </script>

+ 3 - 2
src/views/todo/seats/accept/Citizen-portrait.vue

@@ -15,7 +15,7 @@
 						</p>
 						<p class="form-item">
 							<span class="form-label">首次联系:</span>
-							<span class="flex-1">{{ formatDate(state.citizen.oneCallTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+							<span class="flex-1">{{ formatDate(state.oneCallTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
 						</p>
 						<p class="form-item">
 							<span class="form-label">上次联系:</span>
@@ -126,6 +126,7 @@ const props = defineProps({
 
 const state = reactive<any>({
 	lastCallTime: '', // 上次联系时间
+  oneCallTime: '', // 首次联系时间
 	hotspotNames: [], // 关注诉求
 	order: {
 		allOrderNum: 0, // 全部工单
@@ -140,7 +141,6 @@ const state = reactive<any>({
 	},
 	citizen: {
 		name: '', // 姓名
-    oneCallTime: '', // 首次联系时间
 		id: '', // 市民id
 	},
 	label: [], // 市民标签
@@ -170,6 +170,7 @@ const getDetail = async (phone: string) => {
 		if (result.citizen) {
 			// 有市民信息
 			state.lastCallTime = result.lastCallTime; // 上次联系时间
+      state.oneCallTime = result.oneCallTime; // 首次联系时间
 			state.hotspotNames = result.hotspotNames ? result.hotspotNames.split(',') : []; // 关注诉求
 			state.order = result.order; // 工单历史
 			state.callHistory = result.callHistory; // 来电历史