diff --git a/backend/dvadmin/system/urls.py b/backend/dvadmin/system/urls.py index c9c12e9afc90d76336279c837c2d0be1e2567def..0ee2cb8c7b3f4ded0cdd69e3ffa66d5ef744a27e 100644 --- a/backend/dvadmin/system/urls.py +++ b/backend/dvadmin/system/urls.py @@ -49,7 +49,7 @@ urlpatterns = [ path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})), # path('login_log/', LoginLogViewSet.as_view({'get': 'list'})), # path('login_log//', LoginLogViewSet.as_view({'get': 'retrieve'})), - path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})), + # path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})), path('clause/privacy.html', PrivacyView.as_view()), path('clause/terms_service.html', TermsServiceView.as_view()), ] diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py index da808ace31b75cf11e7259e2e2f4c24543bab036..4db96db3329fade68b4c0256d0e50c8100e1eb41 100644 --- a/backend/dvadmin/utils/filters.py +++ b/backend/dvadmin/utils/filters.py @@ -33,7 +33,7 @@ class CoreModelFilterBankend(BaseFilterBackend): create_datetime_after = request.query_params.get('create_datetime_after', None) create_datetime_before = request.query_params.get('create_datetime_before', None) update_datetime_after = request.query_params.get('update_datetime_after', None) - update_datetime_before = request.query_params.get('update_datetime_after', None) + update_datetime_before = request.query_params.get('update_datetime_before', None) if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]): create_filter = Q() if create_datetime_after and create_datetime_before: diff --git a/web/src/utils/commonCrud.ts b/web/src/utils/commonCrud.ts index 426f6af2d01d29a35c34db3a807413a31359d81f..9a3817bca3284b09993a0770785f70af99675716 100644 --- a/web/src/utils/commonCrud.ts +++ b/web/src/utils/commonCrud.ts @@ -1,259 +1,286 @@ -import {dict} from "@fast-crud/fast-crud"; -import {shallowRef} from 'vue' -import deptFormat from "/@/components/dept-format/index.vue"; +import {dict} from '@fast-crud/fast-crud'; +import {shallowRef} from 'vue'; +import deptFormat from '/@/components/dept-format/index.vue'; -export const commonCrudConfig = (options = { - create_datetime: { - form: false, - table: false, - search: false - }, - update_datetime: { - form: false, - table: false, - search: false - }, - creator_name: { - form: false, - table: false, - search: false - }, - modifier_name: { - form: false, - table: false, - search: false - }, - dept_belong_id: { - form: false, - table: false, - search: false - }, - description: { - form: false, - table: false, - search: false - }, -}) => { - return { - dept_belong_id: { - title: '所属部门', - type: 'dict-tree', - search: { - show: options.dept_belong_id?.search || false - }, - dict: dict({ - url: '/api/system/dept/all_dept/', - isTree: true, - value: 'id', - label: 'name', - children: 'children', - }), - column: { - align: 'center', - width: 300, - show: options.dept_belong_id?.table || false, - component: { - name: shallowRef(deptFormat), - vModel: "modelValue", - } - }, - form: { - show: options.dept_belong_id?.form || false, - component: { - multiple: false, - clearable: true, - props: { - checkStrictly: true, - props: { - // 为什么这里要写两层props - // 因为props属性名与fs的动态渲染的props命名冲突,所以要多写一层 - label: "name", - value: "id", - } - } - }, - helper: "默认不填则为当前创建用户的部门ID" - } - }, - description: { - title: '备注', - search: { - show: options.description?.search || false - }, - type: 'textarea', - column: { - width: 100, - show: options.description?.table || false, - }, - form: { - show: options.description?.form || false, - component: { - placeholder: '请输入内容', - showWordLimit: true, - maxlength: '200', - } - }, - viewForm: { - show: true - } - }, - modifier_name: { - title: '修改人', - search: { - show: options.modifier_name?.search || false - }, - column: { - width: 100, - show: options.modifier_name?.table || false, - }, - form: { - show: false, - }, - viewForm: { - show: true - } - }, - creator_name: { - title: '创建人', - search: { - show: options.creator_name?.search || false - }, - column: { - width: 100, - show: options.creator_name?.table || false, - }, - form: { - show: false, - }, - viewForm: { - show: true - } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - search: { - show: options.update_datetime?.search || false, - col: {span: 8}, - component: { - type: 'datetimerange', - props: { - 'start-placeholder': '开始时间', - 'end-placeholder': '结束时间', - 'value-format': 'YYYY-MM-DD HH:mm:ss', - 'picker-options': { - shortcuts: [{ - text: '最近一周', - onClick(picker) { - const end = new Date(); - const start = new Date(); - start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); - picker.$emit('pick', [start, end]); - } - }, { - text: '最近一个月', - onClick(picker) { - const end = new Date(); - const start = new Date(); - start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); - picker.$emit('pick', [start, end]); - } - }, { - text: '最近三个月', - onClick(picker) { - const end = new Date(); - const start = new Date(); - start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); - picker.$emit('pick', [start, end]); - } - }] - } - } - }, - valueResolve(context: any) { - const {key, value} = context - //value解析,就是把组件的值转化为后台所需要的值 - //在form表单点击保存按钮后,提交到后台之前执行转化 - if (value) { - context.form.update_datetime_after = value[0] - context.form.update_datetime_before = value[1] - } - // ↑↑↑↑↑ 注意这里是form,不是row - } - }, - column: { - width: 160, - show: options.update_datetime?.table || false, - }, - form: { - show: false, - }, - viewForm: { - show: true - } - }, - create_datetime: { - title: '创建时间', - type: 'datetime', - search: { - show: options.create_datetime?.search || false, - col: {span: 8}, - component: { - type: 'datetimerange', - props: { - 'start-placeholder': '开始时间', - 'end-placeholder': '结束时间', - 'value-format': 'YYYY-MM-DD HH:mm:ss', - 'picker-options': { - shortcuts: [{ - text: '最近一周', - onClick(picker) { - const end = new Date(); - const start = new Date(); - start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); - picker.$emit('pick', [start, end]); - } - }, { - text: '最近一个月', - onClick(picker) { - const end = new Date(); - const start = new Date(); - start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); - picker.$emit('pick', [start, end]); - } - }, { - text: '最近三个月', - onClick(picker) { - const end = new Date(); - const start = new Date(); - start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); - picker.$emit('pick', [start, end]); - } - }] - } - } - }, - valueResolve(context: any) { - const {key, value} = context - //value解析,就是把组件的值转化为后台所需要的值 - //在form表单点击保存按钮后,提交到后台之前执行转化 - if (value) { - context.form.create_datetime_after = value[0] - context.form.create_datetime_before = value[1] - } - // ↑↑↑↑↑ 注意这里是form,不是row - } - }, - column: { - width: 160, - show: options.create_datetime?.table || false, - }, - form: { - show: false - }, - viewForm: { - show: true - } - } - } +/** 1. 每个字段可选属性 */ +export interface CrudFieldOption { + form?: boolean; + table?: boolean; + search?: boolean; + width?: number; } + +/** 2. 总配置接口 */ +export interface CrudOptions { + create_datetime?: CrudFieldOption; + update_datetime?: CrudFieldOption; + creator_name?: CrudFieldOption; + modifier_name?: CrudFieldOption; + dept_belong_id?: CrudFieldOption; + description?: CrudFieldOption; +} + +/** 3. 默认完整配置 */ +const defaultOptions: Required = { + create_datetime: { form: false, table: false, search: false, width: 160 }, + update_datetime: { form: false, table: false, search: false, width: 160 }, + creator_name: { form: false, table: false, search: false, width: 100 }, + modifier_name: { form: false, table: false, search: false, width: 100 }, + dept_belong_id: { form: false, table: false, search: false, width: 300 }, + description: { form: false, table: false, search: false, width: 100 }, +}; + +/** 4. mergeOptions 函数 */ +function mergeOptions(baseOptions: Required, userOptions: CrudOptions = {}): Required { + const result = { ...baseOptions }; + for (const key in userOptions) { + if (Object.prototype.hasOwnProperty.call(userOptions, key)) { + const baseField = result[key as keyof CrudOptions]; + const userField = userOptions[key as keyof CrudOptions]; + if (baseField && userField) { + result[key as keyof CrudOptions] = { ...baseField, ...userField }; + } + } + } + return result; +} + +/** + * 最终暴露的 commonCrudConfig + * @param options 用户自定义配置(可传可不传,不传就用默认) + */ +export const commonCrudConfig = (options: CrudOptions = {}) => { + // ① 合并 + const merged = mergeOptions(defaultOptions, options); + + // ② 用 merged 中的值生成真正的 CRUD 配置 + return { + dept_belong_id: { + title: '所属部门', + type: 'dict-tree', + search: { + show: merged.dept_belong_id.search, + }, + dict: dict({ + url: '/api/system/dept/all_dept/', + isTree: true, + value: 'id', + label: 'name', + children: 'children', + }), + column: { + align: 'center', + width: merged.dept_belong_id.width, + show: merged.dept_belong_id.table, + component: { + // fast-crud里自定义组件常用"component.is" + is: shallowRef(deptFormat), + vModel: 'modelValue', + }, + }, + form: { + show: merged.dept_belong_id.form, + component: { + multiple: false, + clearable: true, + props: { + checkStrictly: true, + props: { + label: 'name', + value: 'id', + }, + }, + }, + helper: '默认不填则为当前创建用户的部门ID', + }, + }, + description: { + title: '备注', + search: { + show: merged.description.search, + }, + type: 'textarea', + column: { + width: merged.description.width, + show: merged.description.table, + }, + form: { + show: merged.description.form, + component: { + placeholder: '请输入内容', + showWordLimit: true, + maxlength: '200', + }, + }, + viewForm: { + show: true, + }, + }, + + modifier_name: { + title: '修改人', + search: { + show: merged.modifier_name.search, + }, + column: { + width: merged.modifier_name.width, + show: merged.modifier_name.table, + }, + form: { + show: merged.modifier_name.form, + }, + viewForm: { + show: true, + }, + }, + + creator_name: { + title: '创建人', + search: { + show: merged.creator_name.search, + }, + column: { + width: merged.creator_name.width, + show: merged.creator_name.table, + }, + form: { + show: merged.creator_name.form, + }, + viewForm: { + show: true, + }, + }, + + update_datetime: { + title: '更新时间', + type: 'datetime', + search: { + show: merged.update_datetime.search, + col: { span: 8 }, + component: { + type: 'datetimerange', + props: { + 'start-placeholder': '开始时间', + 'end-placeholder': '结束时间', + 'value-format': 'YYYY-MM-DD HH:mm:ss', + 'picker-options': { + shortcuts: [ + { + text: '最近一周', + onClick(picker: any) { + const end = new Date(); + const start = new Date(); + start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); + picker.$emit('pick', [start, end]); + }, + }, + { + text: '最近一个月', + onClick(picker: any) { + const end = new Date(); + const start = new Date(); + start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); + picker.$emit('pick', [start, end]); + }, + }, + { + text: '最近三个月', + onClick(picker: any) { + const end = new Date(); + const start = new Date(); + start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); + picker.$emit('pick', [start, end]); + }, + }, + ], + }, + }, + }, + valueResolve(context: any) { + const { value } = context; + if (value) { + context.form.update_datetime_after = value[0]; + context.form.update_datetime_before = value[1]; + delete context.form.update_datetime; + } + }, + }, + column: { + width: merged.update_datetime.width, + show: merged.update_datetime.table, + }, + form: { + show: merged.update_datetime.form, + }, + viewForm: { + show: true, + }, + }, + + create_datetime: { + title: '创建时间', + type: 'datetime', + search: { + show: merged.create_datetime.search, + col: { span: 8 }, + component: { + type: 'datetimerange', + props: { + 'start-placeholder': '开始时间', + 'end-placeholder': '结束时间', + 'value-format': 'YYYY-MM-DD HH:mm:ss', + 'picker-options': { + shortcuts: [ + { + text: '最近一周', + onClick(picker: any) { + const end = new Date(); + const start = new Date(); + start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); + picker.$emit('pick', [start, end]); + }, + }, + { + text: '最近一个月', + onClick(picker: any) { + const end = new Date(); + const start = new Date(); + start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); + picker.$emit('pick', [start, end]); + }, + }, + { + text: '最近三个月', + onClick(picker: any) { + const end = new Date(); + const start = new Date(); + start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); + picker.$emit('pick', [start, end]); + }, + }, + ], + }, + }, + }, + valueResolve(context: any) { + const { value } = context; + if (value) { + context.form.create_datetime_after = value[0]; + context.form.create_datetime_before = value[1]; + delete context.form.create_datetime; + } + }, + }, + column: { + width: merged.create_datetime.width, + show: merged.create_datetime.table, + }, + form: { + show: merged.create_datetime.form, + }, + viewForm: { + show: true, + }, + }, + }; +}; diff --git a/web/src/utils/upgrade.ts b/web/src/utils/upgrade.ts index f21c7788e20b83c5661da47ae31154edc5e99f4e..60c10f21aa66cabe8c9d12f5edcc2d4d5beca9c2 100644 --- a/web/src/utils/upgrade.ts +++ b/web/src/utils/upgrade.ts @@ -1,55 +1,58 @@ -import axios from "axios"; -import * as process from "process"; -import {Local, Session} from '/@/utils/storage'; -import {ElNotification} from "element-plus"; -import fs from "fs"; +import axios from 'axios'; +import * as process from 'process'; +import { Local, Session } from '/@/utils/storage'; +import { ElNotification } from 'element-plus'; +import fs from 'fs'; // 是否显示升级提示信息框 const IS_SHOW_UPGRADE_SESSION_KEY = 'isShowUpgrade'; -const VERSION_KEY = 'DVADMIN3_VERSION' -const VERSION_FILE_NAME = 'version-build' +const VERSION_KEY = 'DVADMIN3_VERSION'; +const VERSION_FILE_NAME = 'version-build'; -export function showUpgrade () { - const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false - if (isShowUpgrade) { - Session.remove(IS_SHOW_UPGRADE_SESSION_KEY) - ElNotification({ - title: '新版本升级', - message: "检测到系统新版本,正在更新中!不用担心,更新很快的哦!", - type: 'success', - duration: 5000, - }); - } +const META_ENV = import.meta.env; + +export function showUpgrade() { + const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false; + if (isShowUpgrade) { + Session.remove(IS_SHOW_UPGRADE_SESSION_KEY); + ElNotification({ + title: '新版本升级', + message: '检测到系统新版本,正在更新中!不用担心,更新很快的哦!', + type: 'success', + duration: 5000, + }); + } } // 生产环境前端版本校验, -export async function checkVersion(){ - if (process.env.NODE_ENV === 'development') { - // 开发环境无需校验前端版本 - return - } - // 获取线上版本号 t为时间戳,防止缓存 - await axios.get(`${import.meta.env.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then(res => { - const {status, data} = res || {} - if (status === 200) { - // 获取当前版本号 - const localVersion = Local.get(VERSION_KEY) - // 将当前版本号持久缓存至本地 - Local.set(VERSION_KEY, data) - // 当用户本地存在版本号并且和线上版本号不一致时,进行页面刷新操作 - if (localVersion && localVersion !== data) { - // 本地缓存版本号和线上版本号不一致,弹出升级提示框 - // 此处无法直接使用消息框进行提醒,因为 window.location.reload()会导致消息框消失,将在loading页面判断是否需要显示升级提示框 - Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true) - window.location.reload() - - } - } - }) +export async function checkVersion() { + if (META_ENV.MODE === 'development') { + // 开发环境无需校验前端版本 + return; + } + // 获取线上版本号 t为时间戳,防止缓存 + await axios.get(`${META_ENV.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then((res) => { + const { status, data } = res || {}; + if (status === 200) { + // 获取当前版本号 + const localVersion = Local.get(VERSION_KEY); + // 将当前版本号持久缓存至本地 + Local.set(VERSION_KEY, data); + // 当用户本地存在版本号并且和线上版本号不一致时,进行页面刷新操作 + if (localVersion && localVersion !== data) { + // 本地缓存版本号和线上版本号不一致,弹出升级提示框 + // 此处无法直接使用消息框进行提醒,因为 window.location.reload()会导致消息框消失,将在loading页面判断是否需要显示升级提示框 + Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true); + window.location.reload(); + } + } + }); } -export function generateVersionFile (){ - // 生成版本文件到public目录下version文件中 - const version = `${process.env.npm_package_version}.${new Date().getTime()}`; - fs.writeFileSync(`public/${VERSION_FILE_NAME}`, version); +export function generateVersionFile() { + // 生成版本文件到public目录下version文件中 + const package_version = META_ENV?.npm_package_version ?? process.env?.npm_package_version; + + const version = `${package_version}.${new Date().getTime()}`; + fs.writeFileSync(`public/${VERSION_FILE_NAME}`, version); }