瀏覽代碼

3.7.1版本发布

JEECG 7 月之前
父節點
當前提交
13cb18b707
共有 100 個文件被更改,包括 7917 次插入6864 次删除
  1. 1 0
      jeecgboot-vue3/package.json
  2. 6060 6410
      jeecgboot-vue3/pnpm-lock.yaml
  3. 3 1
      jeecgboot-vue3/src/components/ContextMenu/src/ContextMenu.vue
  4. 3 0
      jeecgboot-vue3/src/components/Form/src/BasicForm.vue
  5. 14 1
      jeecgboot-vue3/src/components/Form/src/components/FormAction.vue
  6. 39 9
      jeecgboot-vue3/src/components/Form/src/components/FormItem.vue
  7. 15 7
      jeecgboot-vue3/src/components/Form/src/components/Middleware.vue
  8. 18 1
      jeecgboot-vue3/src/components/Form/src/container/JFormContainer.vue
  9. 2 2
      jeecgboot-vue3/src/components/Form/src/hooks/useForm.ts
  10. 18 0
      jeecgboot-vue3/src/components/Form/src/hooks/useFormEvents.ts
  11. 23 5
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JAreaLinkage.vue
  12. 4 1
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JCategorySelect.vue
  13. 22 13
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JCheckbox.vue
  14. 7 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JCodeEditor.vue
  15. 2 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JEditor.vue
  16. 10 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JImageUpload.vue
  17. 2 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JInputSelect.vue
  18. 3 1
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopup.vue
  19. 3 1
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopupDict.vue
  20. 4 2
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JRangeNumber.vue
  21. 88 7
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JSearchSelect.vue
  22. 5 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JSelectDept.vue
  23. 11 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JSelectMultiple.vue
  24. 4 1
      jeecgboot-vue3/src/components/Form/src/jeecg/components/JUpload/JUpload.vue
  25. 30 3
      jeecgboot-vue3/src/components/Form/src/jeecg/components/base/JSelectBiz.vue
  26. 1 1
      jeecgboot-vue3/src/components/Form/src/jeecg/components/modal/DeptSelectModal.vue
  27. 59 6
      jeecgboot-vue3/src/components/Form/src/jeecg/components/modal/JPopupOnlReportModal.vue
  28. 13 0
      jeecgboot-vue3/src/components/Form/src/jeecg/components/modal/UserSelectModal.vue
  29. 11 1
      jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useCodeHinting.ts
  30. 2 2
      jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useSelectBiz.ts
  31. 4 1
      jeecgboot-vue3/src/components/Form/src/types/form.ts
  32. 7 2
      jeecgboot-vue3/src/components/Form/src/utils/Area.ts
  33. 1 1
      jeecgboot-vue3/src/components/Form/src/utils/areaDataUtil.js
  34. 2 0
      jeecgboot-vue3/src/components/Icon/src/IconList.vue
  35. 9 8
      jeecgboot-vue3/src/components/Icon/src/IconPicker.vue
  36. 3 0
      jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeDepartSelectCell.vue
  37. 3 0
      jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeUserSelectCell.vue
  38. 40 4
      jeecgboot-vue3/src/components/Table/src/components/TableAction.vue
  39. 48 20
      jeecgboot-vue3/src/components/Table/src/components/settings/ColumnSetting.vue
  40. 19 3
      jeecgboot-vue3/src/components/Table/src/hooks/useColumns.ts
  41. 8 1
      jeecgboot-vue3/src/components/Table/src/hooks/useColumnsCache.ts
  42. 53 27
      jeecgboot-vue3/src/components/Table/src/hooks/useCustomSelection.tsx
  43. 5 0
      jeecgboot-vue3/src/components/Table/src/types/table.ts
  44. 1 0
      jeecgboot-vue3/src/components/Table/src/types/tableAction.ts
  45. 2 28
      jeecgboot-vue3/src/components/Tinymce/src/Editor.vue
  46. 22 26
      jeecgboot-vue3/src/components/Tinymce/src/ImgUpload.vue
  47. 0 99
      jeecgboot-vue3/src/components/Tinymce/src/ProcessMask.vue
  48. 1 1
      jeecgboot-vue3/src/components/Upload/src/UploadModal.vue
  49. 2 0
      jeecgboot-vue3/src/components/jeecg/AiChat/components/chat.vue
  50. 2 0
      jeecgboot-vue3/src/components/jeecg/AiChat/components/slide.vue
  51. 10 2
      jeecgboot-vue3/src/components/jeecg/AiChat/index.vue
  52. 19 4
      jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue
  53. 3 1
      jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts
  54. 63 29
      jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useDragSort.ts
  55. 37 8
      jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useMethods.ts
  56. 23 0
      jeecgboot-vue3/src/components/jeecg/JVxeTable/utils.ts
  57. 53 1
      jeecgboot-vue3/src/components/jeecg/OnLine/JPopupOnlReport.vue
  58. 53 4
      jeecgboot-vue3/src/components/jeecg/OnLine/hooks/usePopBiz.ts
  59. 8 6
      jeecgboot-vue3/src/components/jeecg/comment/CommentFiles.vue
  60. 12 2
      jeecgboot-vue3/src/components/jeecg/comment/CommentList.vue
  61. 3 2
      jeecgboot-vue3/src/components/jeecg/comment/CommentPanel.vue
  62. 57 22
      jeecgboot-vue3/src/components/jeecg/comment/MyComment.vue
  63. 1 0
      jeecgboot-vue3/src/components/jeecg/comment/useComment.ts
  64. 4 0
      jeecgboot-vue3/src/enums/cacheEnum.ts
  65. 9 0
      jeecgboot-vue3/src/hooks/system/useJvxeMethods.ts
  66. 3 1
      jeecgboot-vue3/src/hooks/web/usePermission.ts
  67. 4 5
      jeecgboot-vue3/src/hooks/web/useWebSocket.ts
  68. 4 1
      jeecgboot-vue3/src/store/modules/user.ts
  69. 38 0
      jeecgboot-vue3/src/utils/areaData/pcaUtils.ts
  70. 29 1
      jeecgboot-vue3/src/utils/common/compUtils.ts
  71. 4 2
      jeecgboot-vue3/src/utils/common/vxeUtils.ts
  72. 113 1
      jeecgboot-vue3/src/utils/index.ts
  73. 8 7
      jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts
  74. 3 1
      jeecgboot-vue3/src/views/monitor/mynews/index.vue
  75. 11 2
      jeecgboot-vue3/src/views/monitor/mynews/mynews.data.ts
  76. 8 2
      jeecgboot-vue3/src/views/monitor/quartz/index.vue
  77. 84 0
      jeecgboot-vue3/src/views/monitor/route/components/RouteRecycleBinModal.vue
  78. 21 1
      jeecgboot-vue3/src/views/monitor/route/index.vue
  79. 39 0
      jeecgboot-vue3/src/views/monitor/route/route.api.ts
  80. 1 1
      jeecgboot-vue3/src/views/monitor/route/route.data.ts
  81. 1 1
      jeecgboot-vue3/src/views/system/appconfig/ThirdAppDingTalkConfigForm.vue
  82. 2 2
      jeecgboot-vue3/src/views/system/category/category.data.ts
  83. 7 1
      jeecgboot-vue3/src/views/system/depart/components/DepartLeftTree.vue
  84. 122 22
      jeecgboot-vue3/src/views/system/depart/components/DepartRuleTab.vue
  85. 148 12
      jeecgboot-vue3/src/views/system/departUser/components/DepartRoleAuthDrawer.vue
  86. 1 1
      jeecgboot-vue3/src/views/system/departUser/components/DepartRoleInfoTab.vue
  87. 10 3
      jeecgboot-vue3/src/views/system/departUser/components/DepartUserInfoTab.vue
  88. 57 6
      jeecgboot-vue3/src/views/system/dict/components/DictRecycleBinModal.vue
  89. 21 0
      jeecgboot-vue3/src/views/system/dict/dict.api.ts
  90. 2 2
      jeecgboot-vue3/src/views/system/dict/dict.data.ts
  91. 8 2
      jeecgboot-vue3/src/views/system/dict/index.vue
  92. 23 1
      jeecgboot-vue3/src/views/system/menu/DataRuleList.vue
  93. 8 3
      jeecgboot-vue3/src/views/system/menu/index.vue
  94. 39 1
      jeecgboot-vue3/src/views/system/menu/menu.data.ts
  95. 3 0
      jeecgboot-vue3/src/views/system/notice/DetailModal.vue
  96. 5 2
      jeecgboot-vue3/src/views/system/notice/NoticeModal.vue
  97. 1 1
      jeecgboot-vue3/src/views/system/notice/index.vue
  98. 19 1
      jeecgboot-vue3/src/views/system/notice/notice.data.ts
  99. 6 1
      jeecgboot-vue3/src/views/system/role/components/RoleUserTable.vue
  100. 0 0
      jeecgboot-vue3/src/views/system/role/components/UseSelectModal.vue

+ 1 - 0
jeecgboot-vue3/package.json

@@ -32,6 +32,7 @@
     "ant-design-vue": "^4.1.2",
     "axios": "^1.6.7",
     "china-area-data": "^5.0.1",
+    "@vant/area-data": "^1.5.1",
     "clipboard": "^2.0.11",
     "codemirror": "^5.65.3",
     "cron-parser": "^4.9.0",

File diff suppressed because it is too large
+ 6060 - 6410
jeecgboot-vue3/pnpm-lock.yaml


+ 3 - 1
jeecgboot-vue3/src/components/ContextMenu/src/ContextMenu.vue

@@ -138,7 +138,9 @@
   .item-style() {
     li {
       display: inline-block;
-      width: 100%;
+      //update-begin---author:wangshuai---date:2024-06-24---for:【TV360X-1576】右键样式选中缺少了一块---
+      width: 100% !important;
+      //update-end---author:wangshuai---date:2024-06-24---for:【TV360X-1576】右键样式选中缺少了一块---
       height: @default-height;
       margin: 0 !important;
       line-height: @default-height;

+ 3 - 0
jeecgboot-vue3/src/components/Form/src/BasicForm.vue

@@ -10,6 +10,7 @@
           :formProps="getProps"
           :allDefaultValues="defaultValueRef"
           :formModel="formModel"
+          :formName="getBindValue.name"
           :setFormModel="setFormModel"
           :validateFields="validateFields"
           :clearValidate="clearValidate"
@@ -213,6 +214,7 @@
         getFieldsValue,
         updateSchema,
         resetSchema,
+        getSchemaByField,
         appendSchemaByField,
         removeSchemaByFiled,
         resetFields,
@@ -308,6 +310,7 @@
         resetSchema,
         setProps,
         getProps,
+        getSchemaByField,
         removeSchemaByFiled,
         appendSchemaByField,
         clearValidate,

+ 14 - 1
jeecgboot-vue3/src/components/Form/src/components/FormAction.vue

@@ -1,6 +1,6 @@
 <template>
   <a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
-    <div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
+    <div class="btnArea" style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
       <FormItem>
         <!-- update-begin-author:zyf   Date:20211213  for:调换按钮前后位置-->
         <slot name="submitBefore"></slot>
@@ -126,3 +126,16 @@
     },
   });
 </script>
+<style lang="less" scoped>
+  // update-begin--author:liaozhiyang---date:20240617---for:【TV360X-999】在1753px宽度下 流程设计页面查询的展开换行了
+  .btnArea {
+    :deep(.ant-form-item-control-input-content) {
+      display: flex;
+      align-items: center;
+      .ant-btn-link {
+        padding-left: 0;
+      }
+    }
+  }
+  // update-end--author:liaozhiyang---date:20240617---for:【TV360X-999】在1753px宽度下 流程设计页面查询的展开换行了
+</style>

+ 39 - 9
jeecgboot-vue3/src/components/Form/src/components/FormItem.vue

@@ -18,6 +18,8 @@
   import { useAppInject } from '/@/hooks/web/useAppInject';
   import { usePermission } from '/@/hooks/web/usePermission';
   import Middleware from './Middleware.vue';
+  import { useLocaleStoreWithOut } from '/@/store/modules/locale';
+
   export default defineComponent({
     name: 'BasicFormItem',
     inheritAttrs: false,
@@ -58,10 +60,16 @@
         default: null,
       },
       // update-end-author:liaozhiyang---date:20240605---for:【TV360X-857】解决禁用状态下触发校验
+      // update-begin--author:liaozhiyang---date:20240625---for:【TV360X-1511】blur不生效
+      formName: {
+        type: String,
+        default: '',
+      },
+      // update-end--author:liaozhiyang---date:20240625---for:【TV360X-1511】blur不生效
     },
     setup(props, { slots }) {
       const { t } = useI18n();
-
+      const localeStore = useLocaleStoreWithOut();
       const { schema, formProps } = toRefs(props) as {
         schema: Ref<FormSchema>;
         formProps: Ref<FormProps>;
@@ -306,7 +314,7 @@
       }
 
       function renderComponent() {
-        const { renderComponentContent, component, field, changeEvent = 'change', valueField, componentProps, dynamicRules } = props.schema;
+        const { renderComponentContent, component, field, changeEvent = 'change', valueField, componentProps, dynamicRules, rules:defRules = [] } = props.schema;
 
         const isCheck = component && ['Switch', 'Checkbox'].includes(component);
         // update-begin--author:liaozhiyang---date:20231013---for:【QQYUN-6679】input去空格
@@ -316,6 +324,10 @@
         }
         // update-end--author:liaozhiyang---date:20231013---for:【QQYUN-6679】input去空格
         const eventKey = `on${upperFirst(changeEvent)}`;
+        const getRules = (): ValidationRule[] => {
+          const dyRules = isFunction(dynamicRules) ? dynamicRules(unref(getValues)) : [];
+          return [...dyRules, ...defRules];
+        };
         // update-begin--author:liaozhiyang---date:20230922---for:【issues/752】表单校验dynamicRules 无法 使用失去焦点后校验 trigger: 'blur'
         const on = {
           [eventKey]: (...args: Nullable<Recordable>[]) => {
@@ -337,9 +349,14 @@
             }
             // update-end--author:liaozhiyang---date:20231013---for:【QQYUN-6679】input去空格
             props.setFormModel(field, value);
-            // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-341】有值之后必填校验不消失
-            props.validateFields([field]).catch((_) => {});
-            // update-end--author:liaozhiyang---date:20240522--for:【TV360X-341】有值之后必填校验不消失
+            // update-begin--author:liaozhiyang---date:20240625---for:【TV360X-1511】blur不生效
+            const findItem = getRules().find((item) => item?.trigger === 'blur');
+            if (!findItem) {
+              // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-341】有值之后必填校验不消失
+              props.validateFields([field]).catch((_) => {});
+              // update-end--author:liaozhiyang---date:20240625---for:【TV360X-341】有值之后必填校验不消失
+            }
+            // update-end--author:liaozhiyang---date:20240625---for:【TV360X-1511】blur不生效
           },
           // onBlur: () => {
           //   props.validateFields([field], { triggerName: 'blur' }).catch((_) => {});
@@ -366,11 +383,21 @@
         }
         // update-end--author:liaozhiyang---date:20240308---for:【QQYUN-8377】formSchema props支持动态修改
 
-        const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
+        // update-begin--author:sunjianlei---date:20240725---for:【TV360X-972】控件禁用时统一占位内容
+        // const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
+        const isCreatePlaceholder = !!autoSetPlaceHolder;
+        // update-end----author:sunjianlei---date:20240725---for:【TV360X-972】控件禁用时统一占位内容
+
         // RangePicker place是一个数组
         if (isCreatePlaceholder && component !== 'RangePicker' && component) {
           //自动设置placeholder
-          propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component) + props.schema.label;
+          // update-begin--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+          let label = isFunction(props.schema.label) ? props.schema.label() : props.schema.label;
+          if (localeStore.getLocale === 'en' && !(/^\s/.test(label))) {
+            label = ' ' + label;
+          }
+          // update-end--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+          propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component) + label;
         }
         propsData.codeField = field;
         propsData.formValues = unref(getValues);
@@ -403,7 +430,10 @@
       function renderLabelHelpMessage() {
         //update-begin-author:taoyan date:2022-9-7 for: VUEN-2061【样式】online表单超出4个 .. 省略显示
         //label宽度支持自定义
-        const { label, helpMessage, helpComponentProps, subLabel, labelLength } = props.schema;
+        const { label: itemLabel, helpMessage, helpComponentProps, subLabel, labelLength } = props.schema;
+        // update-begin--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+        const label = isFunction(itemLabel) ? itemLabel() : itemLabel;
+        // update-end--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
         let showLabel: string = label + '';
         // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-98】label展示的文字必须和labelLength配置一致
         if (labelLength) {
@@ -469,7 +499,7 @@
               <div style="display:flex">
                 {/* author: sunjianlei for: 【VUEN-744】此处加上 width: 100%; 因为要防止组件宽度超出 FormItem */}
                 {/* update-begin--author:liaozhiyang---date:20240510---for:【TV360X-719】表单校验不通过项滚动到可视区内 */}
-                <Middleware>{getContent()}</Middleware>
+                <Middleware formName={props.formName} fieldName={field}>{getContent()}</Middleware>
                 {/* update-end--author:liaozhiyang---date:20240510---for:【TV360X-719】表单校验不通过项滚动到可视区内 */}
                 {showSuffix && <span class="suffix">{getSuffix}</span>}
               </div>

+ 15 - 7
jeecgboot-vue3/src/components/Form/src/components/Middleware.vue

@@ -5,12 +5,20 @@
 </template>
 
 <script setup>
-  import { Form } from 'ant-design-vue';
-  import { computed } from 'vue';
-  const formItemContext = Form.useInjectFormItemContext();
-  const formItemId = computed(() => {
-    return formItemContext.id.value;
-  });
+  import { ref } from 'vue';
+  // update-begin--author:liaozhiyang---date:20240625---for:【TV360X-1511】blur不生效
+  const formItemId = ref(null);
+  const props = defineProps(['formName', 'fieldName']);
+  if (props.formName && props.fieldName) {
+    formItemId.value = `${props.formName}_${props.fieldName}`;
+  }
+  // update-end--author:liaozhiyang---date:20240625---for:【TV360X-1511】blur不生效
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="less" scoped>
+  // update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1253】代码生成查询区域和新增组件没撑满
+  div > :deep(.ant-picker) {
+    width: 100%;
+  }
+  // update-end--author:liaozhiyang---date:20240617---for:【TV360X-1253】代码生成查询区域和新增组件没撑满
+</style>

+ 18 - 1
jeecgboot-vue3/src/components/Form/src/container/JFormContainer.vue

@@ -1,5 +1,5 @@
 <template>
-  <div :class="formDisabled ? 'jeecg-form-container-disabled jeecg-form-detail-effect' : ''">
+  <div :class="formDisabled ? 'jeecg-form-container-disabled jeecg-form-detail-effect' : 'jeecg-and-modal-form'">
     <fieldset :disabled="formDisabled">
       <slot name="detail"></slot>
     </fieldset>
@@ -39,6 +39,23 @@
 </script>
 
 <style scoped lang="less">
+  // update-begin--author:liaozhiyang---date:20240719---for:【TV360X-1090】表单label超长省略显示
+  .jeecg-and-modal-form {
+    :deep(.ant-form-item-label) {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      padding-right: 6px;
+      > label {
+        line-height: 32px;
+        display: inline;
+        &::after {
+          margin-right: 0;
+        }
+      }
+    }
+  }
+  // update-end--author:liaozhiyang---date:20240719---for:【TV360X-1090】表单label超长省略显示
   .jeecg-form-container-disabled {
     cursor: not-allowed;
   }

+ 2 - 2
jeecgboot-vue3/src/components/Form/src/hooks/useForm.ts

@@ -5,7 +5,7 @@ import { handleRangeValue } from '../utils/formUtils';
 import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
 import { isProdMode } from '/@/utils/env';
 import { error } from '/@/utils/log';
-import { getDynamicProps, getValueType } from '/@/utils';
+import { getDynamicProps, getValueType, getValueTypeBySchema } from '/@/utils';
 import { add } from "/@/components/Form/src/componentMap";
 //集成online专用控件
 import { OnlineSelectCascade, LinkTableCard, LinkTableSelect } from  '@jeecg/online';
@@ -137,7 +137,7 @@ export function useForm(props?: Props): UseFormReturnType {
       let values = form.validate(nameList).then((values) => {
         for (let key in values) {
           if (values[key] instanceof Array) {
-            let valueType = getValueType(getProps, key);
+            let valueType = getValueTypeBySchema(form.getSchemaByField(key)!);
             if (valueType === 'string') {
               values[key] = values[key].join(',');
             }

+ 18 - 0
jeecgboot-vue3/src/components/Form/src/hooks/useFormEvents.ts

@@ -94,6 +94,23 @@ export function useFormEvents({
     });
     validateFields(validKeys).catch((_) => {});
   }
+
+  /**
+   *  根据字段名获取schema
+   * @param field
+   */
+  function getSchemaByField(field: string): Nullable<FormSchema> {
+    if (!isString(field)) {
+      return null
+    }
+    const schemaList: FormSchema[] = unref(getSchema);
+    const index = schemaList.findIndex((schema) => schema.field === field);
+    if (index !== -1) {
+      return cloneDeep(schemaList[index]);
+    }
+    return null
+  }
+
   /**
    * @description: Delete based on field name
    */
@@ -270,6 +287,7 @@ export function useFormEvents({
     getFieldsValue,
     updateSchema,
     resetSchema,
+    getSchemaByField,
     appendSchemaByField,
     removeSchemaByFiled,
     resetFields,

+ 23 - 5
jeecgboot-vue3/src/components/Form/src/jeecg/components/JAreaLinkage.vue

@@ -22,7 +22,7 @@
       showArea: propTypes.bool.def(true),
       //是否是全部
       showAll: propTypes.bool.def(false),
-      // 存储数据 
+      // 存储数据 (all时:传递到外面的是数组;province, city, region传递外面的是字符串)
       saveCode: propTypes.oneOf(['province', 'city', 'region', 'all']).def('all'),
     },
     emits: ['options-change', 'change', 'update:value'],
@@ -97,6 +97,25 @@
         }
         return result;
       }
+      /**
+       * liaozhiyang
+       * 2024-06-17
+       * 【TV360X-1224】省市区组件默认传到外面的值是字符串逗号分隔
+       * */
+      const send = (data) => {
+        let result = data;
+        if (result) {
+          if (props.saveCode === 'all') {
+            // 传递的是数组
+          } else {
+            // 传递的是字符串
+            result = data.join(',');
+          }
+        }
+        emit('change', result);
+        emit('update:value', result);
+      };
+
       function handleChange(arr, ...args) {
         // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-501】省市区换新组件
         if (arr?.length) {
@@ -111,11 +130,9 @@
           } else {
             result = arr;
           }
-          emit('change', result);
-          emit('update:value', result);
+          send(result);
         } else {
-          emit('change', arr);
-          emit('update:value', arr);
+          send(arr);
         }
         // update-end--author:liaozhiyang---date:20240607---for:【TV360X-501】省市区换新组件
         // emitData.value = args;
@@ -124,6 +141,7 @@
         // state.value = result;
         //update-end-author:taoyan date:2022-6-27 for: VUEN-1424【vue3】树表、单表、jvxe、erp 、内嵌子表省市县 选择不上
       }
+      
       return {
         cascaderValue,
         attrs,

+ 4 - 1
jeecgboot-vue3/src/components/Form/src/jeecg/components/JCategorySelect.vue

@@ -84,7 +84,9 @@
         () => {
           loadItemByCode();
         },
-        { deep: true }
+        //update-begin---author:wangshuai---date:2024-06-17---for:【TV360X-480】封装表单和原生表单,默认值生成有问题的字段:分类字典树附默认值不生效---
+        { deep: true, immediate: true }
+        //update-end---author:wangshuai---date:2024-06-17---for:【TV360X-480】封装表单和原生表单,默认值生成有问题的字段:分类字典树附默认值不生效---
       );
       watch(
         () => props.pcode,
@@ -124,6 +126,7 @@
             treeValue.value = { value: null, label: null };
           }
         } else {
+          console.log("props.value:::",props.value)
           loadDictItem({ ids: props.value }).then((res) => {
             let values = props.value.split(',');
             treeValue.value = res.map((item, index) => ({

+ 22 - 13
jeecgboot-vue3/src/components/Form/src/jeecg/components/JCheckbox.vue

@@ -10,7 +10,7 @@
   import { defineComponent, computed, watch, watchEffect, ref, unref } from 'vue';
   import { propTypes } from '/@/utils/propTypes';
   import { useAttrs } from '/@/hooks/core/useAttrs';
-  import { initDictOptions } from '/@/utils/dict/index';
+  import {getDictItems} from "@/api/common/api";
 
   export default defineComponent({
     name: 'JCheckbox',
@@ -67,21 +67,30 @@
         }
         //根据字典Code, 初始化选项
         if (props.dictCode) {
-          const dictData = await initDictOptions(props.dictCode);
-          checkOptions.value = dictData.reduce((prev, next) => {
-            if (next) {
-              const value = next['value'];
-              prev.push({
-                label: next['text'],
-                value: value,
-                color: next['color'],
-              });
-            }
-            return prev;
-          }, []);
+          loadDictOptions()
         }
       }
 
+      // 根据字典code查询字典项
+      function loadDictOptions() {
+        //update-begin-author:taoyan date:2022-6-21 for: 字典数据请求前将参数编码处理,但是不能直接编码,因为可能之前已经编码过了
+        let temp = props.dictCode || '';
+        if (temp.indexOf(',') > 0 && temp.indexOf(' ') > 0) {
+          // 编码后 是不包含空格的
+          temp = encodeURI(temp);
+        }
+        //update-end-author:taoyan date:2022-6-21 for: 字典数据请求前将参数编码处理,但是不能直接编码,因为可能之前已经编码过了
+        getDictItems(temp).then((res) => {
+          if (res) {
+            checkOptions.value = res.map((item) => ({value: item.value, label: item.text, color: item.color}));
+            //console.info('res', dictOptions.value);
+          } else {
+            console.error('getDictItems error: : ', res);
+            checkOptions.value = [];
+          }
+        });
+      }
+
       /**
        * change事件
        * @param $event

+ 7 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/JCodeEditor.vue

@@ -44,6 +44,9 @@
   import 'codemirror/addon/hint/anyword-hint.js';
   // 匹配括号
   import 'codemirror/addon/edit/matchbrackets';
+  // 占位符
+  import 'codemirror/addon/display/placeholder.js';
+  
   import { useAttrs } from '/@/hooks/core/useAttrs';
   import { useDesign } from '/@/hooks/web/useDesign';
   import { isJsonObjectString } from '/@/utils/is.ts';
@@ -356,6 +359,10 @@
     .CodeMirror{
       border: 1px solid #ddd;
     }
+    .CodeMirror pre.CodeMirror-placeholder {
+      color: #cacaca;
+      font-family: -apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
+    }
   }
   .CodeMirror-hints.idea,
   .CodeMirror-hints.monokai {

+ 2 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/JEditor.vue

@@ -17,6 +17,8 @@
     props: {
       value: propTypes.string.def(''),
       disabled: propTypes.bool.def(false),
+      //是否聚焦
+      autoFocus: propTypes.bool.def(true),
     },
     emits: ['change', 'update:value'],
     setup(props, { emit, attrs }) {

+ 10 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/JImageUpload.vue

@@ -183,6 +183,16 @@
         if (file.status === 'error') {
           createMessage.error(`${file.name} 上传失败.`);
         }
+        // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1640】上传图片大小超出限制显示优化
+        if (file.status === 'done' && file.response.success === false) {
+          const failIndex = uploadFileList.value.findIndex((item) => item.uid === file.uid);
+          if (failIndex != -1) {
+            uploadFileList.value.splice(failIndex, 1);
+          }
+          createMessage.warning(file.response.message);
+          return;
+        }
+        // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1640】上传图片大小超出限制显示优化
         let fileUrls = [];
         let noUploadingFileCount = 0;
         if (file.status != 'uploading') {

+ 2 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/JInputSelect.vue

@@ -17,6 +17,7 @@
         v-if="selectLocation === 'right'"
         v-model:value="selectVal"
         @change="handleSelectChange"
+        :style="{width:props.selectWidth}"
       >
         <a-select-option v-for="item in options" :key="item.value">{{ item.label }}</a-select-option>
       </a-select>
@@ -33,6 +34,7 @@
     selectLocation: propTypes.oneOf(['left', 'right']).def('right'),
     selectPlaceholder: propTypes.string.def(''),
     inputPlaceholder: propTypes.string.def(''),
+    selectWidth:propTypes.string.def('auto'),
   });
   const emit = defineEmits(['update:value', 'change']);
   const selectVal = ref<string>();

+ 3 - 1
jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopup.vue

@@ -23,8 +23,9 @@
         :groupId="uniqGroupId"
         :param="param"
         :showAdvancedButton="showAdvancedButton"
-        @ok="callBack"
         :getContainer="getContainer"
+        :getFormValues="getFormValues"
+        @ok="callBack"
       ></JPopupOnlReportModal>
     </a-form-item>
     <!-- update-end--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
@@ -56,6 +57,7 @@
       groupId: propTypes.string.def(''),
       formElRef: propTypes.object,
       setFieldsValue: propTypes.func,
+      getFormValues: propTypes.func,
       getContainer: propTypes.func,
       fieldConfig: {
         type: Array,

+ 3 - 1
jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopupDict.vue

@@ -15,9 +15,10 @@
         :sorter="sorter"
         :groupId="''"
         :param="param"
-        @ok="callBack"
+        :getFormValues="getFormValues"
         :getContainer="getContainer"
         :showAdvancedButton="showAdvancedButton"
+        @ok="callBack"
       />
     </a-form-item>
     <!-- update-end--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
@@ -56,6 +57,7 @@
       multi: propTypes.bool.def(false),
       param: propTypes.object.def({}),
       spliter: propTypes.string.def(','),
+      getFormValues: propTypes.func,
       getContainer: propTypes.func,
       showAdvancedButton: propTypes.bool.def(true),
     },

+ 4 - 2
jeecgboot-vue3/src/components/Form/src/jeecg/components/JRangeNumber.vue

@@ -39,8 +39,10 @@
 
       function emitArray() {
         let arr = [];
-        let begin = beginValue.value || '';
-        let end = endValue.value || '';
+        // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1749】数量0输入不了,输入清空了
+        let begin = beginValue.value ?? '';
+        let end = endValue.value ?? '';
+        // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1749】数量0输入不了,输入清空了
         arr.push(begin);
         arr.push(end);
         emit('change', arr);

+ 88 - 7
jeecgboot-vue3/src/components/Form/src/jeecg/components/JSearchSelect.vue

@@ -14,11 +14,12 @@
     @focus="handleAsyncFocus"
     @search="loadData"
     @change="handleAsyncChange"
+    @popupScroll="handlePopupScroll"
   >
     <template #notFoundContent>
       <a-spin size="small" />
     </template>
-    <a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
+    <a-select-option v-for="d in options" :key="d?.value" :value="d?.value">{{ d?.text }}</a-select-option>
   </a-select>
   <!--字典下拉搜素-->
   <a-select
@@ -36,7 +37,7 @@
     <template #notFoundContent>
       <a-spin v-if="loading" size="small" />
     </template>
-    <a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
+    <a-select-option v-for="d in options" :key="d?.value" :value="d?.value">{{ d?.text }}</a-select-option>
   </a-select>
 </template>
 
@@ -95,6 +96,11 @@
       const lastLoad = ref(0);
       // 是否根据value加载text
       const loadSelectText = ref(true);
+      // 异步(字典表) - 滚动加载时会用到
+      let isHasData = true;
+      let scrollLoading = false;
+      let pageNo = 1;
+      let searchKeyword = '';
 
       // 是否是字典表
       const isDictTable = computed(() => {
@@ -152,6 +158,12 @@
         if (!isDictTable.value) {
           return;
         }
+        // update-begin--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+        pageNo = 1;
+        isHasData = true;
+        searchKeyword = value;
+        // update-end--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+ 
         lastLoad.value += 1;
         const currentLoad = unref(lastLoad);
         options.value = [];
@@ -164,7 +176,7 @@
         defHttp
           .get({
             url: `/sys/dict/loadDict/${props.dict}`,
-            params: { keyword: keywordInfo, pageSize: props.pageSize },
+            params: { keyword: keywordInfo, pageSize: props.pageSize, pageNo },
           })
           .then((res) => {
             loading.value = false;
@@ -173,6 +185,13 @@
                 return;
               }
               options.value = res;
+              // update-begin--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+              pageNo++;
+              // update-end--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+            } else {
+              // update-begin--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+              pageNo == 1 && (isHasData = false);
+              // update-end--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
             }
           });
       }, 300);
@@ -195,10 +214,12 @@
                   key: value,
                   label: res,
                 };
-                selectedAsyncValue.value = { ...obj };
+                if (props.value == value) {
+                  selectedAsyncValue.value = { ...obj };
+                }
                 //update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
                 if(props.immediateChange == true){
-                  emit('change', value);
+                  emit('change', props.value);
                 }
                 //update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
               }
@@ -243,18 +264,31 @@
           if (!dict) {
             console.error('搜索组件未配置字典项');
           } else {
+            // update-begin--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+            pageNo = 1;
+            isHasData = true;
+            searchKeyword = '';
+            // update-end--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+
             //异步一开始也加载一点数据
             loading.value = true;
             let keywordInfo = getKeywordParam('');
             defHttp
               .get({
                 url: `/sys/dict/loadDict/${dict}`,
-                params: { pageSize: pageSize, keyword: keywordInfo },
+                params: { pageSize: pageSize, keyword: keywordInfo, pageNo },
               })
               .then((res) => {
                 loading.value = false;
                 if (res && res.length > 0) {
                   options.value = res;
+                  // update-begin--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+                  pageNo++;
+                  // update-end--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+                } else {
+                  // update-begin--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+                  pageNo == 1 && (isHasData = false);
+                  // update-end--author:liaozhiyang---date:20240731---for:【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
                 }
               });
           }
@@ -355,11 +389,57 @@
       // update-begin--author:liaozhiyang---date:20240523---for:【TV360X-26】下拉搜索控件选中选项后再次点击下拉应该显示初始的下拉选项,而不是只展示选中结果
       const handleAsyncFocus = () => {
         // update-begin--author:liaozhiyang---date:20240709---for:【issues/6681】异步查询不生效
-        (isObject(selectedAsyncValue.value) || selectedAsyncValue.value?.length) && isDictTable.value && props.async && initDictTableData();
+        if ((isObject(selectedAsyncValue.value) || selectedAsyncValue.value?.length) && isDictTable.value && props.async) {
+          // update-begin--author:liaozhiyang---date:20240809---for:【TV360X-2062】下拉搜索选择第二页数据后,第一次点击时(得到焦点)滚动条没复原到初始位置且数据会加载第二页数据(应该只加载第一页数据)
+          options.value = [];
+          // update-end--author:liaozhiyang---date:20240809---for:【TV360X-2062】下拉搜索选择第二页数据后,第一次点击时(得到焦点)滚动条没复原到初始位置且数据会加载第二页数据(应该只加载第一页数据)
+          initDictTableData();
+        }
         // update-end--author:liaozhiyang---date:20240709---for:【issues/6681】异步查询不生效
         attrs.onFocus?.();
       };
       // update-end--author:liaozhiyang---date:20240523---for:【TV360X-26】下拉搜索控件选中选项后再次点击下拉应该显示初始的下拉选项,而不是只展示选中结果
+
+      /**
+       * 2024-07-30
+       * liaozhiyang
+       * 【TV360X-1898】JsearchSelect组件传入字典表格式则支持滚动加载
+       * */
+      const handlePopupScroll = async (e) => {
+        // 字典表才才支持滚动加载
+        if (isDictTable.value) {
+          const { target } = e;
+          const { scrollTop, scrollHeight, clientHeight } = target;
+          if (!scrollLoading && isHasData && scrollTop + clientHeight >= scrollHeight - 10) {
+            scrollLoading = true;
+            let keywordInfo = getKeywordParam(searchKeyword);
+
+            defHttp
+              .get({ url: `/sys/dict/loadDict/${props.dict}`, params: { pageSize: props.pageSize, keyword: keywordInfo, pageNo } })
+              .then((res) => {
+                loading.value = false;
+                if (res?.length > 0) {
+                  // 防止开源只更新了前端代码没更新后端代码(第一页和第二页面的第一条数据相同则是后端代码没更新,没分页)
+                  if (JSON.stringify(res[0]) === JSON.stringify(options.value[0])) {
+                    isHasData =  false;
+                    return;
+                  }
+                  options.value.push(...res);
+                  pageNo++;
+                } else {
+                  isHasData = false;
+                }
+              })
+              .finally(() => {
+                scrollLoading = false;
+              })
+              .catch(() => {
+                pageNo != 1 && pageNo--;
+              });
+          }
+        }
+      };
+
       return {
         attrs,
         options,
@@ -373,6 +453,7 @@
         handleChange,
         handleAsyncChange,
         handleAsyncFocus,
+        handlePopupScroll,
       };
     },
   });

+ 5 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/JSelectDept.vue

@@ -152,6 +152,11 @@
         let result = typeof props.value == 'string' ? values.join(',') : values;
         emit('update:value', result);
         emit('change', result);
+        // update-begin--author:liaozhiyang---date:20240627---for:【TV360X-1648】用户编辑界面“所属部门”与“负责部门”联动出错(同步之前丢的代码)
+        if (!values || values.length == 0) {
+          emit('select', null, null);
+        }
+        // update-end--author:liaozhiyang---date:20240627---for:【TV360X-1648】用户编辑界面“所属部门”与“负责部门”联动出错(同步之前丢的代码)
       };
       // update-end--author:liaozhiyang---date:20240527---for:【TV360X-414】部门设置了默认值,查询重置变成空了(同步JSelectUser组件改法)
       

+ 11 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/JSelectMultiple.vue

@@ -91,6 +91,17 @@
         }
       });
 
+      watch(
+          () => props.dictCode,
+          () => {
+            if (props.dictCode) {
+              loadDictOptions();
+            } else {
+              dictOptions.value = props.options;
+            }
+          }
+      );
+
       watch(
         () => props.value,
         (val) => {

+ 4 - 1
jeecgboot-vue3/src/components/Form/src/jeecg/components/JUpload/JUpload.vue

@@ -305,7 +305,10 @@
     } else if (info.file.status === 'error') {
       createMessage.error(`${info.file.name} 上传失败.`);
     }
-    fileList.value = fileListTemp;
+    // update-begin--author:liaozhiyang---date:20240628---for:【issues/1273】上传组件JUpload配置beforeUpload阻止了上传,前端页面中还是显示缩略图
+    // beforeUpload 返回false,则没有status
+    info.file.status && (fileList.value = fileListTemp);
+    // update-end--author:liaozhiyang---date:20240628---for:【issues/1273】上传组件JUpload配置beforeUpload阻止了上传,前端页面中还是显示缩略图
     if (info.file.status === 'done' || info.file.status === 'removed') {
       //returnUrl为true时仅返回文件路径
       if (props.returnUrl) {

+ 30 - 3
jeecgboot-vue3/src/components/Form/src/jeecg/components/base/JSelectBiz.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <a-row class="j-select-row" type="flex" :gutter="8">
+    <div v-if="isDetailsMode">
+      <p class="detailStr" :title="detailStr">{{ detailStr }}</p>
+    </div>
+    <a-row v-else class="j-select-row" type="flex" :gutter="8">
       <a-col class="left" :class="{ full: !showButton }">
         <!-- 显示加载效果 -->
         <a-input v-if="loading" readOnly placeholder="加载中…">
@@ -32,7 +35,7 @@
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, inject, reactive } from 'vue';
+  import { defineComponent, ref, inject, reactive, watch } from 'vue';
   import { propTypes } from '/@/utils/propTypes';
   import { useAttrs } from '/@/hooks/core/useAttrs';
   import { LoadingOutlined } from '@ant-design/icons-vue';
@@ -59,6 +62,8 @@
       maxTagCount: propTypes.number,
       // buttonIcon
       buttonIcon: propTypes.string.def(''),
+      // 【TV360X-1002】是否是详情模式
+      isDetailsMode: propTypes.bool.def(false),
     },
     emits: ['handleOpen', 'change'],
     setup(props, { emit, refs }) {
@@ -67,7 +72,7 @@
       //接收选择的值
       const selectValues = inject('selectValues') || ref({});
       const attrs = useAttrs();
-
+      const detailStr = ref('');
       /**
        * 打开弹出框
        */
@@ -89,12 +94,28 @@
         emit('change', value);
       }
 
+      // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
+      watch(
+        [selectValues, options],
+        () => {
+          if (props.isDetailsMode) {
+            if (Array.isArray(selectValues.value) && Array.isArray(options.value)) {
+              const result = options.value.map((item) => item.label);
+              detailStr.value = result.join(',');
+            }
+          }
+        },
+        { immediate: true }
+      );
+      // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
+
       return {
         attrs,
         selectValues,
         options,
         handleChange,
         openModal,
+        detailStr,
       };
     },
   });
@@ -119,4 +140,10 @@
       display: none !important;
     }
   }
+  .detailStr {
+    margin: 0;
+    max-width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
 </style>

+ 1 - 1
jeecgboot-vue3/src/components/Form/src/jeecg/components/modal/DeptSelectModal.vue

@@ -80,7 +80,7 @@
       
       const queryUrl = getQueryUrl();
       const [{ visibleChange, checkedKeys, getCheckStrictly, getSelectTreeData, onCheck, onLoadData, treeData, checkALL, expandAll, onSelect }] =
-        useTreeBiz(treeRef, queryUrl, getBindValue, props);
+        useTreeBiz(treeRef, queryUrl, getBindValue, props, emit);
       const searchInfo = ref(props.params);
       const tree = ref([]);
       //替换treeNode中key字段为treeData中对应的字段

+ 59 - 6
jeecgboot-vue3/src/components/Form/src/jeecg/components/modal/JPopupOnlReportModal.vue

@@ -12,8 +12,8 @@
       wrapClassName="j-popup-modal"
       @visible-change="visibleChange"
     >
-      <div class="jeecg-basic-table-form-container" v-if="showSearchFlag">
-        <a-form ref="formRef" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol" @keyup.enter.native="searchQuery">
+      <div class="jeecg-basic-table-form-container">
+        <a-form ref="formRef" v-if="showSearchFlag" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol" @keyup.enter.native="searchQuery">
           <a-row :gutter="24">
             <template v-for="(item, index) in queryInfo">
               <template v-if="item.hidden === '1'">
@@ -31,8 +31,8 @@
             <a-col :md="8" :sm="8" v-if="showAdvancedButton">
               <span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
                 <a-col :lg="6">
-                  <a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset">重置</a-button>
-                  <a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery" style="margin-left: 8px">查询</a-button>
+                  <a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
+                  <a-button preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button>
                   <a @click="handleToggleSearch" style="margin-left: 8px">
                     {{ toggleSearchStatus ? '收起' : '展开' }}
                     <Icon :icon="toggleSearchStatus ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
@@ -59,6 +59,12 @@
         @change="handleChangeInTable"
       >
         <template #tableTitle></template>
+         <template #bodyCell="{text, column}">
+          <template v-if="column.fieldType === 'Image'">
+            <span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
+            <img v-else :src="getImgView(text)" alt="图片不存在" class="cellIamge" @click="viewOnlineCellImage($event, text)" />
+          </template>
+        </template>
       </BasicTable>
     </BasicModal>
   </div>
@@ -71,6 +77,8 @@
   import { useAttrs } from '/@/hooks/core/useAttrs';
   import { usePopBiz } from '/@/components/jeecg/OnLine/hooks/usePopBiz';
   import { useMessage } from '/@/hooks/web/useMessage';
+  import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
+  import { createImgPreview } from '/@/components/Preview/index';
 
   export default defineComponent({
     name: 'JPopupOnlReportModal',
@@ -82,9 +90,9 @@
         loading: true,
       }),
     },
-    props: ['multi', 'code', 'sorter', 'groupId', 'param','showAdvancedButton'],
+    props: ['multi', 'code', 'sorter', 'groupId', 'param','showAdvancedButton', 'getFormValues'],
     emits: ['ok', 'register'],
-    setup(props, { emit, refs }) {
+    setup(props, { emit }) {
       const { createMessage } = useMessage();
       const labelCol = reactive({
         xs: { span: 24 },
@@ -242,6 +250,41 @@
         queryParam.value = {};
         loadData(1);
       }
+
+      /**
+       * 2024-07-24
+       * liaozhiyang
+       * 【TV360X-1756】报表添加图片类型
+       * 图片
+       * @param text
+       */
+      function getImgView(text) {
+        if (text && text.indexOf(',') > 0) {
+          text = text.substring(0, text.indexOf(','));
+        }
+        return getFileAccessHttpUrl(text);
+      }
+      /**
+       * 2024-07-24
+       * liaozhiyang
+       * 【TV360X-1756】报表添加图片类型
+       * 预览列表 cell 图片
+       * @param text
+       */
+      function viewOnlineCellImage(e, text) {
+        e.stopPropagation();
+        if (text) {
+          let imgList: any = [];
+          let arr = text.split(',');
+          for (let str of arr) {
+            if (str) {
+              imgList.push(getFileAccessHttpUrl(str));
+            }
+          }
+          createImgPreview({ imageList: imgList });
+        }
+      }
+
       return {
         attrs,
         register,
@@ -272,6 +315,8 @@
         handleToggleSearch,
         searchQuery,
         searchReset,
+        getImgView,
+        viewOnlineCellImage,
       };
     },
   });
@@ -290,4 +335,12 @@
   :deep(.jeecg-basic-table .ant-table-wrapper .ant-table-title){
     min-height: 0;
   }
+  .cellIamge {
+    height: 25px !important;
+    margin: 0 auto;
+    max-width: 80px;
+    font-size: 12px;
+    font-style: italic;
+    cursor: pointer;
+  }
 </style>

+ 13 - 0
jeecgboot-vue3/src/components/Form/src/jeecg/components/modal/UserSelectModal.vue

@@ -29,6 +29,7 @@
             :rowSelection="rowSelection"
             :indexColumnProps="indexColumnProps"
             :afterFetch="afterFetch"
+            :beforeFetch="beforeFetch"
           >
             <!-- update-begin-author:taoyan date:2022-5-25 for: VUEN-1112一对多 用户选择 未显示选择条数,及清空 -->
             <template #tableTitle></template>
@@ -267,6 +268,17 @@
       maxHeight.value = clientHeight > 600 ? 600 : clientHeight;
       // update-end--author:liaozhiyang---date:20240607---for:【TV360X-305】小屏幕展示10条
 
+      //update-begin---author:wangshuai---date:2024-07-03---for:【TV360X-1629】用户选择组件不是根据创建时间正序排序的---
+      /**
+       * 请求之前根据创建时间排序
+       *
+       * @param params
+       */
+      function beforeFetch(params) {
+        return Object.assign({ column: 'createTime', order: 'desc' }, params);
+      }
+      //update-end---author:wangshuai---date:2024-07-03---for:【TV360X-1629】用户选择组件不是根据创建时间正序排序的---
+
       return {
         //config,
         handleOk,
@@ -287,6 +299,7 @@
         afterFetch,
         handleCancel,
         maxHeight,
+        beforeFetch,
       };
     },
   });

+ 11 - 1
jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useCodeHinting.ts

@@ -39,12 +39,22 @@ export const useCodeHinting = (CodeMirror, keywords, language) => {
           // 查找.前面是否有定义的关键词
           const curLineCode = cm.getLine(cur.line);
           for (let i = 0, len = customKeywords.length; i < len; i++) {
-            const k = curLineCode.substring(-1, customKeywords[i].length);
+            const k = curLineCode.slice(-(customKeywords[i].length + 1), -1);
             if (customKeywords.includes(k)) {
               recordKeyword = k;
               break;
             }
           }
+        } else {
+          // 查找单词前面是否有.this(.关键词)
+          const curLineCode = cm.getLine(cur.line);
+          for (let i = 0, len = customKeywords.length; i < len; i++) {
+            const k = curLineCode.slice(start - (customKeywords[i].length + 1), start);
+            if (k.substr(-1) === '.' && customKeywords.includes(k.replace('.', ''))) {
+              recordKeyword = k.replace('.', '');
+              break;
+            }
+          }
         }
         const findIdx = (a, b) => a.toLowerCase().indexOf(b.toLowerCase());
         let list = currentKeywords.filter((item) => {

+ 2 - 2
jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useSelectBiz.ts

@@ -2,7 +2,7 @@ import { inject, reactive, ref, watch, unref, Ref } from 'vue';
 import { useMessage } from '/@/hooks/web/useMessage';
 import { isEmpty } from '@/utils/is';
 
-export function useSelectBiz(getList, props, emit) {
+export function useSelectBiz(getList, props, emit?) {
   //接收下拉框选项
   const selectOptions = inject('selectOptions', ref<Array<object>>([]));
   //接收已选择的值
@@ -120,7 +120,7 @@ export function useSelectBiz(getList, props, emit) {
       props.showSelected && initSelectRows();
     } else {
       // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入
-      emit('close');
+      emit?.('close');
       // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入
     }
   }

+ 4 - 1
jeecgboot-vue3/src/components/Form/src/types/form.ts

@@ -35,6 +35,7 @@ export interface FormActionType {
   resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
   setProps: (formProps: Partial<FormProps>) => Promise<void>;
   getProps: ComputedRef<Partial<FormProps>>;
+  getSchemaByField: (field: string) => Nullable<FormSchema>;
   removeSchemaByFiled: (field: string | string[]) => Promise<void>;
   appendSchemaByField: (schema: FormSchema, prefixField: string | undefined, first?: boolean | undefined) => Promise<void>;
   validateFields: (nameList?: NamePath[], options?: ValidateOptions) => Promise<any>;
@@ -131,7 +132,9 @@ export interface FormSchema {
   // Variable name bound to v-model Default value
   valueField?: string;
   // Label name
-  label: string | VNode;
+  // update-begin--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+  label: string | VNode | Fn;
+  // update-end--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
   // Auxiliary text
   subLabel?: string;
   // Help text on the right side of the text

+ 7 - 2
jeecgboot-vue3/src/components/Form/src/utils/Area.ts

@@ -1,4 +1,4 @@
-import REGION_DATA from 'china-area-data';
+import {pcaa as REGION_DATA} from "@/utils/areaData/pcaUtils";
 
 /**
  * Area 属性all的类型
@@ -18,7 +18,7 @@ class Area {
 
   /**
    * 构造器
-   * @param express
+   * @param pcaa
    */
   constructor(pcaa?) {
     if (!pcaa) {
@@ -101,6 +101,11 @@ const jeecgAreaData = new Area();
 // 根据code找文本
 const getAreaTextByCode = function (code) {
   let index = 3;
+  // update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1210】online列表香港、澳门没翻译(香港、澳门只有两级其它省份是三级)
+  if (code && ['82', '81'].includes(code.substring(0, 2))) {
+    index = 2;
+  }
+  // update-end--author:liaozhiyang---date:20240617---for:【TV360X-1210】online列表香港、澳门没翻译(香港、澳门只有两级其它省份是三级)
   //update-begin-author:liusq---date:20220531--for: 判断code是否是多code逗号分割的字符串,是的话,获取最后一位的code ---
   if (code && code.includes(',')) {
     index = code.split(",").length;

+ 1 - 1
jeecgboot-vue3/src/components/Form/src/utils/areaDataUtil.js

@@ -1,4 +1,4 @@
-import REGION_DATA from 'china-area-data';
+import {pcaa as REGION_DATA} from "@/utils/areaData/pcaUtils";
 import { cloneDeep } from 'lodash-es';
 
 // code转汉字大对象

+ 2 - 0
jeecgboot-vue3/src/components/Icon/src/IconList.vue

@@ -53,6 +53,7 @@
       Empty,
       Pagination,
     },
+    emits: ['update:value'],
     props: {
       currentList: propTypes.any.def([]),
       clearSelect: propTypes.bool.def(false),
@@ -141,6 +142,7 @@
             }
           }
         }
+        emit('update:value', currentSelect.value);
       }
 
       /**

+ 9 - 8
jeecgboot-vue3/src/components/Icon/src/IconPicker.vue

@@ -11,37 +11,37 @@
     <a-tabs style="padding-left: 15px;padding-right: 15px">
       <a-tab-pane tab="方向性图标" key="1">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="directionIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="directionIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
       <a-tab-pane tab="指示性图标" key="2">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="suggestionIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="suggestionIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
       <a-tab-pane tab="编辑类图标" key="3">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="editIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="editIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
       <a-tab-pane tab="数据类图标" key="4">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="dataIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="dataIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
       <a-tab-pane tab="网站通用图标" key="5">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="webIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="webIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
       <a-tab-pane tab="品牌和标识" key="6">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="logoIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-svg-mode="isSvgMode" :current-list="logoIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
       <a-tab-pane tab="其他" key="7">
         <a-form-item-rest>
-          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-page="true" :is-search="true" :is-svg-mode="isSvgMode" :current-list="otherIcons" :value="currentSelect" />
+          <icon-list ref="iconListRef" :clear-select="clearSelect" :copy="copy" :is-page="true" :is-search="true" :is-svg-mode="isSvgMode" :current-list="otherIcons" v-model:value="selectIcon" />
         </a-form-item-rest>
       </a-tab-pane>
     </a-tabs>
@@ -94,6 +94,7 @@
   const isSvgMode = props.mode === 'svg';
   const icons = isSvgMode ? getSvgIcons() : getIcons();
 
+  const selectIcon = ref('');
   const currentSelect = ref('');
 
   const { t } = useI18n();
@@ -170,7 +171,7 @@
    * 图标弹窗确定事件
    */
   function handleOk() {
-    currentSelect.value = iconListRef.value.getIcon();
+    currentSelect.value = selectIcon.value;
     iconOpen.value = false;
   }
 

+ 3 - 0
jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeDepartSelectCell.vue

@@ -77,6 +77,9 @@
           maxTagCount: 1,
           // 显示提示重写,去掉省略号
           maxTagPlaceholder: ({ length }) => '+' + length,
+          // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
+          isDetailsMode: cellProps.value.disabledTable,
+          // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
         };
       });
 

+ 3 - 0
jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeUserSelectCell.vue

@@ -52,6 +52,9 @@
           maxTagCount: 1,
           // 显示提示重写,去掉省略号
           maxTagPlaceholder: ({ length }) => '+' + length,
+          // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
+          isDetailsMode: cellProps.value.disabledTable,
+          // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
         };
       });
 

+ 40 - 4
jeecgboot-vue3/src/components/Table/src/components/TableAction.vue

@@ -25,6 +25,7 @@
       :dropMenuList="getDropdownList"
       popconfirm
       v-if="dropDownActions && getDropdownList.length > 0"
+      :getPopupContainer="dropdownGetPopupContainer"
     >
       <slot name="more"></slot>
       <!--  设置插槽   -->
@@ -71,6 +72,10 @@
       const { prefixCls } = useDesign('basic-table-action');
       const dropdownCls = `${prefixCls}-dropdown`;
       let table: Partial<TableActionType> = {};
+
+      const tempActionsAuth = {};
+      const tempDropdownListAuth = {};
+
       if (!props.outside) {
         table = useTableContext();
       }
@@ -93,7 +98,17 @@
       const getActions = computed(() => {
         return (toRaw(props.actions) || [])
           .filter((action) => {
-            return hasPermission(action.auth) && isIfShow(action);
+            // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-528】列表配置了权限,在列表行上划过,都会执行hasPermission
+            const auth: any = action.auth;
+            let authResult;
+            if (action.auth && typeof tempActionsAuth[auth] === 'boolean') {
+              authResult = tempActionsAuth[auth];
+            } else {
+              authResult = hasPermission(action.auth);
+              action.auth && (tempActionsAuth[auth] = authResult);
+            }
+            return authResult && isIfShow(action);
+            // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-528】列表配置了权限,在列表行上划过,都会执行hasPermission
           })
           .map((action) => {
             const { popConfirm } = action;
@@ -121,7 +136,17 @@
       const getDropdownList = computed((): any[] => {
         //过滤掉隐藏的dropdown,避免出现多余的分割线
         const list = (toRaw(props.dropDownActions) || []).filter((action) => {
-          return hasPermission(action.auth) && isIfShow(action);
+          // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-528】列表配置了权限,在列表行上划过,都会执行hasPermission
+          const auth: any = action.auth;
+          let authResult;
+          if (action.auth && typeof tempDropdownListAuth[auth] === 'boolean') {
+            authResult = tempDropdownListAuth[auth];
+          } else {
+            authResult = hasPermission(action.auth);
+            action.auth && (tempDropdownListAuth[auth] = authResult);
+          }
+          return authResult && isIfShow(action);
+          // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-528】列表配置了权限,在列表行上划过,都会执行hasPermission
         });
         return list.map((action, index) => {
           const { label, popConfirm } = action;
@@ -129,6 +154,13 @@
           if (popConfirm) {
             const overlayClassName = popConfirm.overlayClassName;
             popConfirm.overlayClassName = `${overlayClassName ? overlayClassName : ''} ${prefixCls}-popconfirm`;
+            // update-begin--author:liaozhiyang---date:20240814---for:【issues/7028】表格全屏后操作列中的下拉菜单和气泡确认框不显示
+            if (!popConfirm.getPopupContainer) {
+              popConfirm.getPopupContainer = () => {
+                return (table as any)?.wrapRef?.value ?? document.body;
+              };
+            }
+            // update-end--author:liaozhiyang---date:20240814---for:【issues/7028】表格全屏后操作列中的下拉菜单和气泡确认框不显示
           }
           // update-end--author:liaozhiyang---date:20240105---for:【issues/951】table删除记录时按钮显示错位
           // update-begin--author:liaozhiyang---date:20240108---for:【issues/936】表格操作栏删除当接口失败时,气泡确认框不会消失
@@ -196,8 +228,12 @@
         });
         isInButton && e.stopPropagation();
       }
-     
-      return { prefixCls, getActions, getDropdownList, getDropdownSlotList, getAlign, onCellClick, getTooltip, dropdownCls };
+      // update-begin--author:liaozhiyang---date:20240814---for:【issues/7028】表格全屏后操作列中的下拉菜单和气泡确认框不显示
+      const dropdownGetPopupContainer = () => {
+        return (table as any)?.wrapRef?.value ?? document.body;
+      };
+      // update-end--author:liaozhiyang---date:20240814---for:【issues/7028】表格全屏后操作列中的下拉菜单和气泡确认框不显示
+      return { prefixCls, getActions, getDropdownList, getDropdownSlotList, getAlign, onCellClick, getTooltip, dropdownCls, dropdownGetPopupContainer };
     },
   });
 </script>

+ 48 - 20
jeecgboot-vue3/src/components/Table/src/components/settings/ColumnSetting.vue

@@ -91,7 +91,7 @@
 </template>
 <script lang="ts">
   import type { BasicColumn, ColumnChangeParam } from '../../types/table';
-  import { defineComponent, ref, reactive, toRefs, watchEffect, nextTick, unref, computed } from 'vue';
+  import { defineComponent, ref, reactive, toRefs, watchEffect, nextTick, unref, computed, watch } from 'vue';
   import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
   import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
   import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
@@ -107,6 +107,7 @@
   import { cloneDeep, omit } from 'lodash-es';
   import Sortablejs from 'sortablejs';
   import type Sortable from 'sortablejs';
+  import { useLocaleStoreWithOut } from '/@/store/modules/locale';
 
   interface State {
     checkAll: boolean;
@@ -156,6 +157,10 @@
 
       const columnListRef = ref<ComponentRef>(null);
 
+      const restAfterOptions = {
+        value: null,
+      };
+
       const state = reactive<State>({
         checkAll: true,
         checkedList: [],
@@ -181,7 +186,7 @@
 
       let sortable: Sortable;
       const sortableOrder = ref<string[]>();
-
+      const localeStore = useLocaleStoreWithOut();
       // 列表字段配置缓存
       const { saveSetting, resetSetting } = useColumnsCache(
         {
@@ -191,6 +196,7 @@
           plainSortOptions,
           sortableOrder,
           checkIndex,
+          restAfterOptions,
         },
         setColumns,
         handleColumnFixed
@@ -210,6 +216,14 @@
         checkIndex.value = !!values.showIndexColumn;
         checkSelect.value = !!values.rowSelection;
       });
+      // update-begin--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+      watch(localeStore, () => {
+        const columns = getColumns();
+        plainOptions.value = columns;
+        plainSortOptions.value = columns;
+        cachePlainOptions.value = columns;
+      });
+      // update-end--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
 
       function getColumns() {
         const ret: Options[] = [];
@@ -227,7 +241,7 @@
         const columns = getColumns();
 
         const checkList = table
-          .getColumns({ ignoreAction: true })
+          .getColumns({ ignoreAction: true, ignoreIndex: true })
           .map((item) => {
             if (item.defaultHidden) {
               return '';
@@ -255,6 +269,9 @@
         }
         state.isInit = true;
         state.checkedList = checkList;
+        // update-begin--author:liaozhiyang---date:20240612---for:【TV360X-105】列展示设置问题[列展示如果存在未勾选的列,保存并刷新后,列展示复选框样式会错乱]
+        state.checkAll = columns.length === checkList.length;
+        // update-end--author:liaozhiyang---date:20240612---for:【TV360X-105】列展示设置问题[列展示如果存在未勾选的列,保存并刷新后,列展示复选框样式会错乱]
       }
 
       // checkAll change
@@ -272,7 +289,9 @@
       const indeterminate = computed(() => {
         const len = plainOptions.value.length;
         let checkedLen = state.checkedList.length;
-        unref(checkIndex) && checkedLen--;
+        // update-begin--author:liaozhiyang---date:20240612---for:【TV360X-105】列展示设置问题[列展示复选框不应该判断序号列复选框的状态]
+        // unref(checkIndex) && checkedLen--;
+        // update-end--author:liaozhiyang---date:20240612---for:【TV360X-105】列展示设置问题[列展示复选框不应该判断序号列复选框的状态]
         return checkedLen > 0 && checkedLen < len;
       });
 
@@ -289,23 +308,29 @@
 
       // reset columns
       function reset() {
-        // state.checkedList = [...state.defaultCheckList];
-        // update-begin--author:liaozhiyang---date:20231103---for:【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
-        state.checkedList = table
-          .getColumns({ ignoreAction: true })
-          .map((item) => {
-            return item.dataIndex || item.title;
-          })
-          .filter(Boolean) as string[];
-        // update-end--author:liaozhiyang---date:20231103---for:【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
-        state.checkAll = true;
-        plainOptions.value = unref(cachePlainOptions);
-        plainSortOptions.value = unref(cachePlainOptions);
+        // update-begin--author:liaozhiyang---date:20240612---for:【TV360X-105】列展示设置问题[需要重置两次才回到初始状态]
         setColumns(table.getCacheColumns());
-        if (sortableOrder.value) {
-          sortable.sort(sortableOrder.value);
-        }
-        resetSetting();
+        setTimeout(() => {
+          const columns = getColumns();
+          // state.checkedList = [...state.defaultCheckList];
+          // update-begin--author:liaozhiyang---date:20231103---for:【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
+          state.checkedList = table
+            .getColumns({ ignoreAction: true })
+            .map((item) => {
+              return item.dataIndex || item.title;
+            })
+            .filter(Boolean) as string[];
+          // update-end--author:liaozhiyang---date:20231103---for:【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
+          state.checkAll = true;
+          plainOptions.value = unref(cachePlainOptions);
+          plainSortOptions.value = unref(cachePlainOptions);
+          restAfterOptions.value = columns;
+          if (sortableOrder.value) {
+            sortable.sort(sortableOrder.value);
+          }
+          resetSetting();
+        }, 100);
+        // update-end--author:liaozhiyang---date:20240612---for:【TV360X-105】列展示设置问题[需要重置两次才回到初始状态]
       }
 
       // Open the pop-up window for drag and drop initialization
@@ -350,6 +375,9 @@
               // setColumns(columns);
               // update-end--author:liaozhiyang---date:20240522---for:【TV360X-108】刷新后勾选之前未勾选的字段拖拽之后该字段对应的表格列消失了
               // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6424】table字段列表设置不显示后,再拖拽字段顺序,原本不显示的,又显示了
+              // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-105】列展示设置问题[重置之后保存的顺序还是上次的]
+              restAfterOptions.value = null;
+              // update-end--author:liaozhiyang---date:20240611---for:【TV360X-105】列展示设置问题[重置之后保存的顺序还是上次的]
             },
           });
           // 记录原始 order 序列

+ 19 - 3
jeecgboot-vue3/src/components/Table/src/hooks/useColumns.ts

@@ -56,14 +56,23 @@ function handleIndexColumn(propsRef: ComputedRef<BasicTableProps>, getPagination
       columns.splice(indIndex, 1);
     }
   });
-
+  // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-105】列展示设置问题[列展示复选框不应该判断序号列复选框的状态]
+  if (columns.length === 0 && showIndexColumn) {
+    const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG);
+    if (indIndex === -1) {
+      pushIndexColumns = true;
+    }
+  }
+  // update-end--author:liaozhiyang---date:20240611---for:【TV360X-105】列展示设置问题[列展示复选框不应该判断序号列复选框的状态]
   if (!pushIndexColumns) return;
 
   const isFixedLeft = columns.some((item) => item.fixed === 'left');
 
   columns.unshift({
     flag: INDEX_COLUMN_FLAG,
-    width: 50,
+    // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1634】密度是宽松模式时,序号列表头换行了
+    width: propsRef.value.size === 'large' ? 65 : 50,
+    // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1634】密度是宽松模式时,序号列表头换行了
     title: t('component.table.index'),
     align: 'center',
     customRender: ({ index }) => {
@@ -107,7 +116,13 @@ export function useColumns(
 
   const getColumnsRef = computed(() => {
     const columns = cloneDeep(unref(columnsRef));
-
+    // update-begin--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+    if (isArray(columns)) {
+      columns.forEach((item) => {
+        item.title = isFunction(item.title) ? item.title() : item.title;
+      });
+    }
+    // update-end--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
     handleIndexColumn(propsRef, getPaginationRef, columns);
     handleActionColumn(propsRef, columns);
     // update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
@@ -346,3 +361,4 @@ export function formatCell(text: string, format: CellFormat, record: Recordable,
     return text;
   }
 }
+

+ 8 - 1
jeecgboot-vue3/src/components/Table/src/hooks/useColumnsCache.ts

@@ -97,7 +97,14 @@ export function useColumnsCache(opt, setColumns, handleColumnFixed) {
   /** 保存列配置 */
   function saveSetting() {
     const { checkedList } = opt.state;
-    const sortedList = unref(opt.plainSortOptions).map((item) => item.value);
+    // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-105】列展示设置问题[重置之后保存的顺序还是上次的]
+    let sortedList = [];
+    if (opt.restAfterOptions.value) {
+      sortedList = opt.restAfterOptions.value.map((item) => item.value);
+    } else {
+      sortedList = unref(opt.plainSortOptions).map((item) => item.value);
+    }
+    // update-end--author:liaozhiyang---date:20240611---for:【TV360X-105】列展示设置问题[重置之后保存的顺序还是上次的]
     $ls.set(cacheKey.value, {
       // 保存的列
       checkedList,

+ 53 - 27
jeecgboot-vue3/src/components/Table/src/hooks/useCustomSelection.tsx

@@ -219,22 +219,27 @@ export function useCustomSelection(
   });
 
   // 选择全部
-  function onSelectAll(checked: boolean) {
+  function onSelectAll(checked: boolean, flag = 'currentPage') {
     // update-begin--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
     if (unref(propsRef)?.rowSelection?.onSelectAll) {
       allSelected = checked;
-      changeRows = getInvertRows(selectedRows.value);
+      changeRows = getInvertRows(selectedRows.value, checked, flag);
     }
     // update-end--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
     // 取消全选
     if (!checked) {
       // update-begin--author:liaozhiyang---date:20240510---for:【issues/1173】取消全选只是当前页面取消
-      // selectedKeys.value = [];
-      // selectedRows.value = [];
-      // emitChange('all');
-      flattedData.value.forEach((item) => {
-        updateSelected(item, false);
-      });
+      // update-begin--author:liaozhiyang---date:20240808---for:【issues/6958】取消没触发onSelectAll事件,跨页选中后 changeRows 为空
+      if (flag === 'allPage') {
+        selectedKeys.value = [];
+        selectedRows.value = [];
+      } else {
+        flattedData.value.forEach((item) => {
+          updateSelected(item, false);
+        });
+      }
+      emitChange('all');
+      // update-end--author:liaozhiyang---date:20240808---for:【issues/6958】取消没触发onSelectAll事件,跨页选中后 changeRows 为空
       // update-end--author:liaozhiyang---date:20240510---for:【issues/1173】取消全选只是当前页面取消
       return;
     }
@@ -310,6 +315,7 @@ export function useCustomSelection(
 
   // 选中单个
   function onSelect(record, checked) {
+    onSelectChild(record, checked);
     updateSelected(record, checked);
     emitChange();
   }
@@ -359,7 +365,25 @@ export function useCustomSelection(
     }
     // update-end--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发
   }
-
+  // update-begin--author:liusq---date:20240819---for:树形表格设置层级关联不生效
+  /**
+   * 层级关联时,选中下级数据
+   * @param record
+   * @param checked
+   */
+  function onSelectChild(record, checked) {
+    if (unref(propsRef)?.isTreeTable && unref(propsRef)?.rowSelection?.checkStrictly && !isRadio.value) {
+      if (record[childrenColumnName.value] && record[childrenColumnName.value].length > 0) {
+        record[childrenColumnName.value].forEach((children) => {
+          updateSelected(children, checked);
+          if (children[childrenColumnName.value] && children[childrenColumnName.value].length > 0) {
+            onSelectChild(children, checked);
+          }
+        });
+      }
+    }
+  }
+  // update-end--author:liusq---date:20240819---for:树形表格设置层级关联不生效
   // 用于判断是否是自定义选择列
   function isCustomSelection(column: BasicColumn) {
     return column.key === CUS_SEL_COLUMN_KEY;
@@ -481,7 +505,7 @@ export function useCustomSelection(
 
   // 清空所有选择
   function clearSelectedRowKeys() {
-    onSelectAll(false);
+    onSelectAll(false, 'allPage');
   }
 
   // 通过 selectedKeys 同步 selectedRows
@@ -536,28 +560,30 @@ export function useCustomSelection(
     return false;
   }
   /**
-   *2023-11-22
+   *2024-08-08
    *廖志阳
-   *根据全选或者反选返回源数据中这次需要变更的数据
+   *根据全选或者反选(或者使用clearSelectedRowKeys()方法)返回源数据中这次需要变更的数据
    */
-  function getInvertRows(rows: any): any {
-    const allRows = findNodeAll(toRaw(unref(flattedData)), () => true, {
-      children: propsRef.value.childrenColumnName ?? 'children',
-    });
-    if (rows.length === 0 || rows.length === allRows.length) {
-      return allRows;
-    } else {
-      const allRowsKey = allRows.map((item) => getRecordKey(item));
-      rows.forEach((rItem) => {
-        const rItemKey = getRecordKey(rItem);
-        const findIndex = allRowsKey.findIndex((item) => rItemKey === item);
-        if (findIndex != -1) {
-          allRowsKey.splice(findIndex, 1);
-          allRows.splice(findIndex, 1);
+  function getInvertRows(selectedRows: any, checked: boolean, flag): any {
+    if (flag == 'currentPage') {
+      const curPageRows = findNodeAll(toRaw(unref(flattedData)), () => true, {
+        children: propsRef.value.childrenColumnName ?? 'children',
+      });
+      const selectedkeys = selectedRows.map((item) => getRecordKey(item));
+      const result: any = [];
+      curPageRows.forEach((item) => {
+        const curRowkey = getRecordKey(item);
+        const index = selectedkeys.findIndex((item) => item === curRowkey);
+        if (index == -1) {
+          checked && result.push(toRaw(item));
+        } else {
+          !checked && result.push(toRaw(item));
         }
       });
+      return result;
+    } else {
+      return toRaw(selectedRows);
     }
-    return allRows;
   }
   function getSelectRows<T = Recordable>() {
     return unref(selectedRows) as T[];

+ 5 - 0
jeecgboot-vue3/src/components/Table/src/types/table.ts

@@ -426,6 +426,9 @@ export interface BasicColumn extends ColumnProps<Recordable> {
 
   //
   flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
+  // update-begin--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
+  title: string | Fn;
+  // update-end--author:liaozhiyang---date:20240724---for:【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换
   customTitle?: VueNode;
 
   slots?: Recordable;
@@ -465,6 +468,8 @@ export interface BasicColumn extends ColumnProps<Recordable> {
     column: BasicColumn;
   }) => any | VNodeChild | JSX.Element;
   // update-end--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
+  // 额外的属性
+  extraProps?: Recordable;
 }
 
 export type ColumnChangeParam = {

+ 1 - 0
jeecgboot-vue3/src/components/Table/src/types/tableAction.ts

@@ -29,4 +29,5 @@ export interface PopConfirm {
   icon?: string;
   placement?: string;
   overlayClassName?: string;
+  getPopupContainer: Fn;
 }

+ 2 - 28
jeecgboot-vue3/src/components/Tinymce/src/Editor.vue

@@ -5,7 +5,6 @@
       <ImgUpload
         :fullscreen="fullscreen"
         @uploading="handleImageUploading"
-        @loading="handleLoading"
         @done="handleDone"
         v-show="editorRef"
         :disabled="disabled"
@@ -14,7 +13,6 @@
     <!-- update-end--author:liaozhiyang---date:20240517---for:【TV360X-35】富文本,图片上传遮挡其他按钮 -->
     <Editor :id="tinymceId" ref="elRef" :disabled="disabled" :init="initOptions" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline"></Editor>
     <slot v-else></slot>
-    <ProcessMask ref="processMaskRef" :show="showUploadMask"/>
   </div>
 </template>
 
@@ -35,7 +33,6 @@
   import 'tinymce/plugins/image';
   import { defineComponent, computed, nextTick, ref, unref, watch, onDeactivated, onBeforeUnmount, onMounted } from 'vue';
   import ImgUpload from './ImgUpload.vue';
-  import ProcessMask from './ProcessMask.vue';
   import {simpleToolbar, menubar, simplePlugins} from './tinymce';
   import { buildShortUUID } from '/@/utils/uuid';
   import { bindHandlers } from './helper';
@@ -85,10 +82,6 @@
       type: Boolean,
       default: true,
     },
-    showUploadMask: {
-      type: Boolean,
-      default: false,
-    },
     //是否聚焦
     autoFocus:{
       type: Boolean,
@@ -98,9 +91,9 @@
 
   export default defineComponent({
     name: 'Tinymce',
-    components: { ImgUpload,Editor,ProcessMask },
+    components: { ImgUpload,Editor },
     inheritAttrs: false,
-    props: tinymceProps as any,
+    props: tinymceProps,
     emits: ['change', 'update:modelValue', 'inited', 'init-error'],
     setup(props, { emit, attrs }) {
       console.log("---Tinymce---初始化---")
@@ -110,7 +103,6 @@
       const tinymceId = ref<string>(buildShortUUID('tiny-vue'));
       const elRef = ref<Nullable<HTMLElement>>(null);
       const editorRootRef = ref<Nullable<HTMLElement>>(null);
-      const processMaskRef = ref<any>(null);
       const imgUploadShow = ref(false);
       const targetElem = ref<null | HTMLDivElement>(null);
 
@@ -333,20 +325,6 @@
         setValue(editor, val);
       }
 
-      /**
-       * 上传进度计算
-       * @param file
-       * @param fileList
-       */
-      function handleLoading(fileLength,showMask){
-        if(fileLength && fileLength > 0){
-          setTimeout(() => {
-              props?.showUploadMask && processMaskRef.value.calcProcess(fileLength)
-          },100)
-        }else{
-           props?.showUploadMask && (processMaskRef.value.showMask = showMask);
-        }
-      }
       function getUploadingImgName(name: string) {
         return `[uploading:${name}]`;
       }
@@ -419,9 +397,6 @@
         editorRootRef,
         imgUploadShow,
         targetElem,
-
-        handleLoading,
-        processMaskRef
       };
     },
   });
@@ -453,7 +428,6 @@
     }
     // update-end--author:liaozhiyang---date:20240527---for:【TV360X-329】富文本禁用状态下工具栏划过边框丢失
   }
-
   html[data-theme='dark'] {
     .@{prefix-cls} {
       .tox .tox-edit-area__iframe {background-color: #141414;}

+ 22 - 26
jeecgboot-vue3/src/components/Tinymce/src/ImgUpload.vue

@@ -8,7 +8,6 @@
       :showUploadList="false"
       :data="getBizData()"
       :headers="getheader()"
-      :before-upload="beforeUpload"
       accept=".jpg,.jpeg,.gif,.png,.webp"
     >
       <a-button type="primary" v-bind="{ ...getButtonProps }">
@@ -38,8 +37,10 @@
         default: false,
       },
     },
-    emits: ['uploading', 'done', 'error', 'loading'],
+    emits: ['uploading', 'done', 'error'],
     setup(props, { emit }) {
+      let uploading = false;
+
       //update-begin-author:taoyan date:2022-5-13 for: 富文本上传图片不支持
       function getheader() {
         return getHeaders();
@@ -66,37 +67,33 @@
         };
       });
 
-      let uploadLength = 0;
       function handleChange({ file, fileList }) {
-        // 过滤掉已经存在的文件
-        fileList = fileList.filter((file) => {
-          const existFile = uploadFileList.value.find(({ uid }) => uid === file.uid);
-          return existFile ? false : true;
-        });
-        uploadLength == 0 && (uploadLength = fileList.length);
-        if (file.status != 'uploading') {
-          emit('loading', uploadLength, true);
+        if (file.status === 'error') {
+          emit('error');
+          uploading = false;
         }
-        // 处理上传好的文件
+        let files = [] as any;
+        let noUploadingFileCount = 0;
         if (file.status != 'uploading') {
           fileList.forEach((file) => {
             if (file.status === 'done' && file.response.success) {
-              const name = file?.name;
-              let realUrl = getFileAccessHttpUrl(file.response.message);
-              uploadFileList.value.push(file);
-              emit('done', name, realUrl);
+              files.push(file);
+            }
+            if (file.status != 'uploading') {
+              noUploadingFileCount++;
             }
           });
         }
+
+        if (noUploadingFileCount == fileList.length) {
+          fileList.forEach((file) => {
+            const name = file?.name;
+            let realUrl = getFileAccessHttpUrl(file.response.message);
+            emit('done', name, realUrl);
+          });
+        }
       }
-      //上传之前
-      function beforeUpload() {
-        uploadLength = 0;
-        emit('loading', null, true);
-        setTimeout(() => {
-          emit('loading', null, false);
-        }, 10000);
-      }
+
       return {
         prefixCls,
         handleChange,
@@ -105,8 +102,7 @@
         getBizData,
         t,
         getButtonProps,
-        uploadFileList,
-        beforeUpload,
+        uploadFileList
       };
     },
   });

+ 0 - 99
jeecgboot-vue3/src/components/Tinymce/src/ProcessMask.vue

@@ -1,99 +0,0 @@
-<template>
-  <div class="mask" v-if="showMask && show">
-    <div class="progress-bar-rear">
-      <div class="progress-bar-front" :style="{ width: progressBarWidth }"></div>
-    </div>
-    <div class="value">{{ percentage }}</div>
-  </div>
-</template>
-<script lang="ts" setup>
-  import { computed, ref } from 'vue';
-
-  const props = defineProps({
-    backColor: {
-      type: [String],
-      default: 'white',
-    },
-    processColor: {
-      type: String,
-      default: '#018FFB',
-    },
-    show: {
-      type: Boolean,
-      default: false,
-    },
-  });
-  //显示遮罩
-  const showMask = ref(false);
-  //进度值占比
-  const progressValue = ref<any>(0);
-  //当前数量
-  const currentNum = ref(0);
-  // 计算进度条宽度的计算属性
-  const progressBarWidth = computed(() => {
-    return progressValue.value > 0 ? `${progressValue.value}px` : '0px';
-  });
-  // 计算进度条百分比
-  const percentage = computed(() => {
-    return `${progressValue.value}%`;
-  });
-  // 进度色
-  const frontColor = computed(() => {
-    return props.processColor;
-  });
-  // 后置背景色
-  const rearColor = computed(() => {
-    return props.backColor;
-  });
-  function calcProcess(totalNum) {
-    !showMask.value && (showMask.value = true);
-    currentNum.value += 1;
-    progressValue.value = ((currentNum.value / totalNum) * 100).toFixed(2);
-    console.log('currentNum.value', currentNum.value);
-    console.log('totalNum.value', totalNum);
-    if (currentNum.value == totalNum) {
-      showMask.value = false;
-      currentNum.value = 0;
-      progressValue.value = 0;
-    }
-  }
-  defineExpose({
-    calcProcess,
-    showMask,
-  });
-</script>
-
-<style lang="less">
-  .mask {
-    position: absolute; /* 或者使用固定定位等其他方式 */
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    background-color: rgba(0, 0, 0, 0.5); /* 半透明遮罩 */
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    overflow: hidden;
-    z-index: 99;
-  }
-
-  .progress-bar-rear {
-    width: 100px; /* 进度条宽度 */
-    height: 10px; /* 进度条高度 */
-    background-color: v-bind(rearColor); /* 进度条颜色 */
-    border-radius: 4px;
-  }
-
-  .progress-bar-front {
-    height: 10px; /* 进度条高度 */
-    background-color: v-bind(frontColor); /* 进度条颜色 */
-    border-radius: 4px;
-  }
-  .value {
-    color: #fff;
-    margin-left: 5px;
-    font-size: 16px;
-    font-weight: 600;
-  }
-</style>

+ 1 - 1
jeecgboot-vue3/src/components/Upload/src/UploadModal.vue

@@ -201,7 +201,7 @@
       // 点击开始上传
       async function handleStartUpload() {
         const { maxNumber } = props;
-        if ((fileListRef.value.length + props.previewFileList?.length ?? 0) > maxNumber) {
+        if ((fileListRef.value.length + (props.previewFileList?.length ?? 0)) > maxNumber) {
           return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
         }
         try {

+ 2 - 0
jeecgboot-vue3/src/components/jeecg/AiChat/components/chat.vue

@@ -158,6 +158,7 @@
   import '../style/style.less';
 
   const props = defineProps(['chatData', 'uuid', 'dataSource']);
+  const emit = defineEmits(['save']);
   const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
   const prompt = ref<string>('');
   const loading = ref<boolean>(false);
@@ -335,6 +336,7 @@
         try {
           return await new Promise<void>((resolve) => {
             props.chatData.length = 0;
+            emit('save');
             resolve();
           });
         } catch {

+ 2 - 0
jeecgboot-vue3/src/components/jeecg/AiChat/components/slide.vue

@@ -102,6 +102,7 @@
 <script setup lang="ts">
   import { ref, watch } from 'vue';
   const props = defineProps(['dataSource']);
+  const emit = defineEmits(['save']);
   const inputRef = ref(null);
   let inputValue = '';
   //新建聊天
@@ -161,6 +162,7 @@
         //  删没了(删除了最后一个)
         props.dataSource.active = null;
       }
+      emit('save');
     }
   };
   watch(

+ 10 - 2
jeecgboot-vue3/src/components/jeecg/AiChat/index.vue

@@ -3,7 +3,7 @@
     <template v-if="dataSource">
       <div class="leftArea" :class="[expand ? 'expand' : 'shrink']">
         <div class="content">
-          <slide v-if="uuid" :dataSource="dataSource"></slide>
+          <slide v-if="uuid" :dataSource="dataSource" @save="handleSave"></slide>
         </div>
         <div class="toggle-btn" @click="handleToggle">
           <span class="icon">
@@ -17,7 +17,7 @@
         </div>
       </div>
       <div class="rightArea" :class="[expand ? 'expand' : 'shrink']">
-        <chat v-if="uuid && chatVisible" :uuid="uuid" :chatData="chatData" :dataSource="dataSource"></chat>
+        <chat v-if="uuid && chatVisible" :uuid="uuid" :chatData="chatData" :dataSource="dataSource" @save="handleSave"></chat>
       </div>
     </template>
     <Spin v-else :spinning="true"></Spin>
@@ -84,6 +84,14 @@
   const save = (content) => {
     defHttp.post({ url: configUrl.save, params: { content: JSON.stringify(content) } }, { isTransformResponse: false });
   };
+  const handleSave = () => {
+    // 删除标签或清空内容之后的保存
+    save(dataSource.value);
+    setTimeout(() => {
+      // 删除标签或清空内容也会触发watch保存,此时不需watch保存需清除
+      clearTimeout(timer);
+    }, 50);
+  };
   // 监听dataSource变化执行操作
   const execute = () => {
     unwatch01 = watch(

+ 19 - 4
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue

@@ -5,15 +5,15 @@
       <template #buttons>
         <div :class="`${prefixCls}-button div`" :size="btnSize">
           <slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize" />
-          <a-button v-if="showAdd" type="primary" preIcon="ant-design:plus-outlined" :disabled="disabled" @click="trigger('add')">
+          <a-button v-if="showAdd" type="primary" preIcon="ant-design:plus-outlined" :disabled="disabled" :loading="deleting" @click="trigger('add')">
             <span>新增</span>
           </a-button>
           <a-button v-if="showSave" preIcon="ant-design:save-outlined" :disabled="disabled" @click="trigger('save')">
             <span>保存</span>
           </a-button>
-          <template v-if="selectedRowIds.length > 0">
-            <Popconfirm v-if="showRemove" :title="`确定要删除这 ${selectedRowIds.length} 项吗?`" @confirm="trigger('remove')">
-              <a-button preIcon="ant-design:minus-outlined" :disabled="disabled">删除</a-button>
+          <template v-if="deleting || selectedRowIds.length > 0">
+            <Popconfirm v-if="showRemove" :title="`确定要删除这 ${selectedRowIds.length} 项吗?`" :disabled="deleting" @confirm="onRemove">
+              <a-button preIcon="ant-design:minus-outlined" :disabled="disabled" :loading="deleting">删除</a-button>
             </Popconfirm>
             <template v-if="showClearSelection">
               <a-button preIcon="ant-design:delete-outlined" @click="trigger('clearSelection')">清空选择</a-button>
@@ -115,4 +115,19 @@
   function toggleCollapse() {
     collapsed.value = !collapsed.value;
   }
+
+  // 【TV360X-1975】在Online设计中,当字段多时,由于会同步删除其他表格导致删除时间变长,所以增加删除loading,防止以为点击删除按钮无效
+  const deleting = ref(false);
+
+  let deleteTimer: any = null
+
+  function onRemove() {
+    trigger('remove')
+    deleting.value = true;
+    if (deleteTimer) {
+      clearTimeout(deleteTimer)
+    }
+    deleteTimer = setTimeout(() => deleting.value = false, 300);
+  }
+
 </script>

+ 3 - 1
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts

@@ -183,7 +183,9 @@ function handleSeqColumn({ props, col, columns }: HandleArgs) {
  */
 function handleSelectionColumn({ props, data, col, columns }: HandleArgs) {
   // 判断是否开启了可选择行
-  if (props.rowSelection) {
+  // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑不显示checkbox
+  if (props.rowSelection && props.disabled == false) {
+    // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑不显示checkbox
     let width = 45;
     if (data.statistics.has && !props.rowExpand && !props.dragSort) {
       width = 60;

+ 63 - 29
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useDragSort.ts

@@ -1,6 +1,7 @@
 import { onMounted, onUnmounted, nextTick } from 'vue';
 import { JVxeTableMethods, JVxeTableProps } from '/@/components/jeecg/JVxeTable/src/types';
 import Sortable from 'sortablejs';
+import { isEnabledVirtualYScroll } from '/@/components/jeecg/JVxeTable/utils';
 
 export function useDragSort(props: JVxeTableProps, methods: JVxeTableMethods) {
   if (props.dragSort) {
@@ -37,40 +38,73 @@ export function useDragSort(props: JVxeTableProps, methods: JVxeTableMethods) {
           // @ts-ignore
           startChildren = [...from.children];
         },
-        onEnd(e) {
-          let oldIndex = e.oldIndex as number;
-          let newIndex = e.newIndex as number;
-          if (oldIndex === newIndex) {
-            return;
-          }
-          // 【VUEN-2505】获取当前行数据
-          let rowNode = xTable.getRowNode(e.item);
-          if (!rowNode) {
-            return;
-          }
-          let from = e.from;
-          let element = startChildren[oldIndex];
-          let target = null;
-          if (oldIndex > newIndex) {
-            // 向上移动
-            if (oldIndex + 1 < startChildren.length) {
-              target = startChildren[oldIndex + 1];
+        onEnd(e: any) {
+          // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-585】拖动字段虚拟滚动不好使
+          const isRealEnabledVirtual = isEnabledVirtualYScroll(props, xTable);
+          let newIndex;
+          let oldIndex;
+          // 滚动排序需要区分当前行编辑是否启动了虚拟滚动(底层loadData方法对是否真实开启了虚拟滚动处理不一样导致需要区分)
+          if (isRealEnabledVirtual) {
+            // e.clone的元素才是真实拖动的元素(虚拟滚动也不会变)
+            const dragNode = e.clone;
+            const dragRowInfo = xTable.getRowNode(dragNode);
+            // e.item的元素只有没虚拟滚动时才是拖动的元素(如果虚拟滚动了则会变)
+            const itemNode = e.item;
+            const itemRowInfo = xTable.getRowNode(itemNode);
+            // e.newIndex是当前可视区内元素的索引(不是数据实际的索引)、e.oldIndex 是拖动时可视区内元素的索引(不是数据实际的索引)
+            if (dragRowInfo!.rowid === itemRowInfo!.rowid) {
+              // e.clone和e.item相同说明拖拽的元素在DOM中,没被虚拟滚动给remove掉。
+              if (e.newIndex === e.oldIndex) {
+                // 此时新旧index一样就可认为没拖动
+                return;
+              }
+            } else {
+            }
+            // 此时真实DOM元素顺序已排(通过拖拽元素的前后元素确定拖拽元素在真实数据中是往前还是往后拖)
+            oldIndex = dragRowInfo!.index;
+            const len = e.from.childNodes.length;
+            let referenceIndex;
+            let referenceNode;
+            if (e.newIndex + 1 < len) {
+              // 拖拽DOM交换之后,后面还有元素(参考物是后面的元素)
+              referenceNode = e.from.childNodes[e.newIndex + 1];
+              referenceIndex = xTable.getRowNode(referenceNode)!.index;
+              if (oldIndex > referenceIndex) {
+                newIndex = referenceIndex;
+              } else {
+                newIndex = referenceIndex - 1;
+              }
+            } else {
+              // 拖拽DOM交换之后,后面没有元素了(参考物是前面的元素)
+              referenceNode = e.from.childNodes[e.newIndex - 1];
+              referenceIndex = xTable.getRowNode(referenceNode)!.index;
+              newIndex = referenceIndex;
             }
           } else {
-            // 向下移动
-            target = startChildren[oldIndex + 1];
+            oldIndex = e.oldIndex;
+            newIndex = e.newIndex;
+            if (oldIndex === newIndex) {
+              return;
+            }
+            const from = e.from;
+            const element = startChildren[oldIndex];
+            let target = null;
+            if (oldIndex > newIndex) {
+              // 向上移动
+              if (oldIndex + 1 < startChildren.length) {
+                target = startChildren[oldIndex + 1];
+              }
+            } else {
+              // 向下移动
+              target = startChildren[oldIndex + 1];
+            }
+            from.removeChild(element);
+            from.insertBefore(element, target);
           }
-          from.removeChild(element);
-          from.insertBefore(element, target);
+          // -update-end--author:liaozhiyang---date:20240620---for:【TV360X-585】拖动字段虚拟滚动不好使
           nextTick(() => {
-            // 【VUEN-2505】算出因虚拟滚动导致的偏移量
-            let diffIndex = rowNode!.index - oldIndex;
-            if (diffIndex > 0) {
-              oldIndex = oldIndex + diffIndex;
-              newIndex = newIndex + diffIndex;
-            }
             methods.doSort(oldIndex, newIndex);
-            methods.trigger('dragged', { oldIndex, newIndex });
+            methods.trigger('dragged', { oldIndex: oldIndex, newIndex: newIndex });
           });
         },
       });

+ 37 - 8
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useMethods.ts

@@ -11,6 +11,7 @@ import { useWebSocket } from './useWebSocket';
 import { getPrefix, getJVxeAuths } from '../utils/authUtils';
 import { excludeKeywords } from '../componentMap';
 import { useColumnsCache } from './useColumnsCache';
+import { isEnabledVirtualYScroll } from '/@/components/jeecg/JVxeTable/utils';
 
 export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps, refs: JVxeRefs, instanceRef: Ref) {
   let xTableTemp: VxeTableInstance & VxeTablePrivateMethods;
@@ -195,7 +196,9 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
       return getEnhanced(column.params.type).aopEvents.activeMethod!.apply(instanceRef.value, arguments as any) ?? true;
     })();
     if (!flag) {
-      getXTable().clearActived();
+      // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
+      getXTable().clearEdit();
+      // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
     }
     return flag;
   }
@@ -424,8 +427,10 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
     // 插入行
     let result = await xTable.insertAt(rows, index);
     if (setActive) {
+      // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
       // 激活最后一行的编辑模式
-      xTable.setActiveRow(result.rows[result.rows.length - 1]);
+      xTable.setEditRow(result.rows[result.rows.length - 1]);
+      // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
     }
     await recalcSortNumber();
     return result;
@@ -453,8 +458,14 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
     callback('', tableData);
   }
 
+  type getTableDataOptions = {
+    rowIds?: string[];
+    // 是否保留新行的id
+    keepNewId?: boolean;
+  }
+
   /** 获取表格数据 */
-  function getTableData(options: any = {}) {
+  function getTableData(options: getTableDataOptions = {}) {
     let { rowIds } = options;
     let tableData;
     // 仅查询指定id的行
@@ -470,7 +481,10 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
       // 查询所有行
       tableData = getXTable().getTableData().fullData;
     }
-    return filterNewRows(tableData, false);
+    return filterNewRows(tableData, {
+      keepNewId: options.keepNewId ?? false,
+      removeNewLine: false,
+    });
   }
 
   /** 仅获取新增的数据 */
@@ -513,23 +527,33 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
     return null;
   }
 
+  type filterNewRowsOptions = {
+    keepNewId?: boolean;
+    removeNewLine?: boolean;
+  } | boolean
+
   /**
    * 过滤添加的行
    * @param rows 要筛选的行数据
-   * @param remove true = 删除新增,false=只删除id
+   * @param optOrRm 如果传 boolean 则是 removeNewLine 参数(true = 删除新增,false=只删除id),如果传对象则是配置参数
    * @param handler function
    */
-  function filterNewRows(rows, remove = true, handler?: Fn) {
+  function filterNewRows(rows, optOrRm:filterNewRowsOptions = true, handler?: Fn) {
     let insertRecords = getXTable().getInsertRecords();
     let records: Recordable[] = [];
+    optOrRm = typeof optOrRm === 'boolean' ? { removeNewLine: optOrRm } : optOrRm;
+    // true = 删除新增,false=只删除id
+    let removeNewLine = optOrRm?.removeNewLine ?? true;
     for (let row of rows) {
       let item = cloneDeep(row);
       if (insertRecords.includes(row)) {
         handler ? handler({ item, row, insertRecords }) : null;
-        if (remove) {
+        if (removeNewLine) {
           continue;
         }
-        delete item.id;
+        if (!optOrRm?.keepNewId) {
+          delete item.id;
+        }
       }
       records.push(item);
     }
@@ -762,6 +786,11 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
       if (xTable.keepSource) {
         sort(xTable.internalData.tableSourceData);
       }
+      // -update-begin--author:liaozhiyang---date:20240620---for:【TV360X-585】拖动字段虚拟滚动不好使
+      if (isEnabledVirtualYScroll(props, xTable)) {
+        await xTable.loadData(xTable.internalData.tableFullData);
+      }
+      // -update-end--author:liaozhiyang---date:20240620---for:【TV360X-585】拖动字段虚拟滚动不好使
       return await recalcSortNumber(force);
     }
   }

+ 23 - 0
jeecgboot-vue3/src/components/jeecg/JVxeTable/utils.ts

@@ -107,3 +107,26 @@ export function vModel(value, row, column: Ref<any> | string) {
   let property = isRef(column) ? column.value.property : column;
   unref(row)[property] = value;
 }
+
+/**
+ * liaozhiyang
+ * 2024-06-20
+ * 判断当前行编辑是否使用了虚拟滚动(并不是开启了就是,还得满足数据数量大于gt值)
+ */
+export function isEnabledVirtualYScroll(props, xTable): boolean {
+  let isRealEnabledVirtual = false;
+  const isEnabledVScroll = props?.scrollY?.enabled;
+  // 100是底层的默认值
+  const gtYNum = props?.scrollY?.gt || 100;
+  if (isEnabledVScroll) {
+    const tableFullData = xTable.internalData.tableFullData;
+    if (gtYNum === 0) {
+      isRealEnabledVirtual = true;
+    } else {
+      if (tableFullData.length > gtYNum) {
+        isRealEnabledVirtual = true;
+      }
+    }
+  }
+  return isRealEnabledVirtual;
+}

+ 53 - 1
jeecgboot-vue3/src/components/jeecg/OnLine/JPopupOnlReport.vue

@@ -48,6 +48,12 @@
       <template #tableTitle>
         <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
       </template>
+      <template #bodyCell="{ text, column }">
+        <template v-if="column.fieldType === 'Image'">
+          <span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
+          <img v-else :src="getImgView(text)" alt="图片不存在" class="cellIamge" @click="viewOnlineCellImage($event, text)" />
+        </template>
+      </template>
     </BasicTable>
 
     <!-- 跳转Href的动态组件方式 -->
@@ -64,7 +70,9 @@
   import { usePopBiz } from '/@/components/jeecg/OnLine/hooks/usePopBiz';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useRoute } from 'vue-router';
-  
+  import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
+  import { createImgPreview } from '/@/components/Preview/index';
+
   export default defineComponent({
     name: 'JPopupOnlReport',
     components: {
@@ -198,6 +206,40 @@
         loadData(1);
       }
 
+      /**
+       * 2024-07-24
+       * liaozhiyang
+       * 【TV360X-1756】报表添加图片类型
+       * 图片
+       * @param text
+       */
+      function getImgView(text) {
+        if (text && text.indexOf(',') > 0) {
+          text = text.substring(0, text.indexOf(','));
+        }
+        return getFileAccessHttpUrl(text);
+      }
+      /**
+       * 2024-07-24
+       * liaozhiyang
+       * 【TV360X-1756】报表添加图片类型
+       * 预览列表 cell 图片
+       * @param text
+       */
+      function viewOnlineCellImage(e, text) {
+        e.stopPropagation();
+        if (text) {
+          let imgList: any = [];
+          let arr = text.split(',');
+          for (let str of arr) {
+            if (str) {
+              imgList.push(getFileAccessHttpUrl(str));
+            }
+          }
+          createImgPreview({ imageList: imgList });
+        }
+      }
+
       return {
         attrs,
 
@@ -229,6 +271,8 @@
         searchQuery,
         searchReset,
         onExportXls,
+        getImgView,
+        viewOnlineCellImage,
       };
     },
   });
@@ -251,4 +295,12 @@
   :deep(.ant-select-selector){
     min-width: 95px;
   }
+  .cellIamge {
+    height: 25px !important;
+    margin: 0 auto;
+    max-width: 80px;
+    font-size: 12px;
+    font-style: italic;
+    cursor: pointer;
+  }
 </style>

+ 53 - 4
jeecgboot-vue3/src/components/jeecg/OnLine/hooks/usePopBiz.ts

@@ -9,6 +9,7 @@ import { useRouter, useRoute } from 'vue-router';
 import { useMethods } from '/@/hooks/system/useMethods';
 import { importViewsFile, _eval } from '/@/utils';
 import {getToken} from "@/utils/auth";
+import {replaceUserInfoByExpression} from "@/utils/common/compUtils";
 
 export function usePopBiz(ob, tableRef?) {
   // update-begin--author:liaozhiyang---date:20230811---for:【issues/675】子表字段Popup弹框数据不更新
@@ -188,7 +189,8 @@ export function usePopBiz(ob, tableRef?) {
    * 加载列信息
    */
   function loadColumnsInfo() {
-    let url = `${configUrl.getColumns}${props.code}`;
+    const {code} = handleCodeParams(true)
+    let url = `${configUrl.getColumns}${code}`;
     //缓存key
     let groupIdKey = props.groupId ? `${props.groupId}${url}` : '';
     httpGroupRequest(() => defHttp.get({ url }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => {
@@ -240,6 +242,11 @@ export function usePopBiz(ob, tableRef?) {
     // 第一次加载 置空isTotal 在这里调用确保 该方法只是进入页面后 加载一次 其余查询不走该方法
     pagination.isTotal = '';
     let url = `${configUrl.getColumnsAndData}${props.id}`;
+
+    const {query} = handleCodeParams()
+    if (query) {
+      url = url + query
+    }
     //缓存key
     let groupIdKey = props.groupId ? `${props.groupId}${url}` : '';
     httpGroupRequest(() => defHttp.get({ url }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => {
@@ -281,6 +288,39 @@ export function usePopBiz(ob, tableRef?) {
     });
   }
 
+  // 处理动态参数和系统变量
+  function handleCodeParams(onlyCode: boolean = false) {
+    if (!props.code) {
+      return {code: '', query: ''}
+    }
+    const firstIndex = props.code.indexOf('?')
+    if (firstIndex === -1) {
+      return {code: props.code, query: ''}
+    }
+    const code = props.code.substring(0, firstIndex)
+    if (onlyCode) {
+      return {code: code, query: ''}
+    }
+    const queryOrigin = props.code.substring(firstIndex, props.code.length);
+    let query: string
+    // 替换系统变量
+    query = replaceUserInfoByExpression(queryOrigin)
+    // 获取表单值
+    if (typeof props.getFormValues === 'function') {
+      const values = props.getFormValues()
+      // 替换动态参数,如果有 ${xxx} 则替换为实际值
+      query = query.replace(/\${([^}]+)}/g, (_$0, $1) => {
+        if (values[$1] == null) {
+          return ''
+        }
+        return values[$1]
+      });
+
+    }
+
+    return {code, query, queryOrigin}
+  }
+
   /**
    * 处理求和的列 合计逻辑 [待优化 3.0]
    */
@@ -502,10 +542,15 @@ export function usePopBiz(ob, tableRef?) {
     // 【VUEN-1568】如果选中了某些行,就只导出选中的行
     let keys = unref(checkedKeys);
     if (keys.length > 0) {
-      params['force_id'] = keys
+      keys = keys
         .map((i) => selectRows.value.find((item) => combineRowKey(item) === i)?.id)
-        .filter((i) => i != null && i !== '')
-        .join(',');
+        .filter((i) => i != null && i !== '');
+      // 判断是否有ID字段
+      if (keys.length === 0) {
+        createMessage.warning('由于数据中缺少ID字段,故无法使用选中导出功能');
+        return;
+      }
+      params['force_id'] = keys.join(',');
     }
     handleExportXls(title.value, url, params);
   }
@@ -592,6 +637,10 @@ export function usePopBiz(ob, tableRef?) {
     // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-578】online报表SQL翻译,第二页不翻页数据
     let url = `${configUrl.getColumnsAndData}${unref(cgRpConfigId)}`;
     // update-end--author:liaozhiyang---date:20240603---for:【TV360X-578】online报表SQL翻译,第二页不翻页数据
+    const {query} = handleCodeParams()
+    if (query) {
+      url = url + query
+    }
     //缓存key
     let groupIdKey = props.groupId ? `${props.groupId}${url}${JSON.stringify(params)}` : '';
     httpGroupRequest(() => defHttp.get({ url, params }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => {

+ 8 - 6
jeecgboot-vue3/src/components/jeecg/comment/CommentFiles.vue

@@ -87,6 +87,7 @@
       HistoryFileList,
     },
     props: {
+      tableId: propTypes.string.def(''),
       tableName: propTypes.string.def(''),
       dataId: propTypes.string.def(''),
       datetime:  propTypes.number.def(1)
@@ -146,14 +147,15 @@
       
       function onSelectFileOk(temp) {
         // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-935】从知识库选择文件判断下是否没选
-        if (temp.id === '') return;
+        if (temp.length === 0) return;
         // update-end--author:liaozhiyang---date:20240603---for:【TV360X-935】从知识库选择文件判断下是否没选
         let arr = selectFileList.value;
-        arr.push({
-          ...temp,
-          exist: true
-        })
-        selectFileList.value = arr;
+        // -update-begin--author:liaozhiyang---date:20240614---for:【TV360X-938】知识库文件选择支持多选
+        temp.forEach((item) => {
+          item.exist = true;
+        });
+        selectFileList.value = [...arr, ...temp];
+        // -update-end--author:liaozhiyang---date:20240614---for:【TV360X-938】知识库文件选择支持多选
       }
 
       return {

+ 12 - 2
jeecgboot-vue3/src/components/jeecg/comment/CommentList.vue

@@ -17,7 +17,7 @@
                   <span>{{ item.toUserId_dictText }}</span>
                   <Tooltip class="comment-last-content" @openChange="(v)=>visibleChange(v, item)">
                     <template #title>
-                      <div v-html="getHtml(item.commentId_dictText)"></div>
+                      <div v-html="getHtml(lineFeed(item.commentId_dictText))"></div>
                     </template>
                     <message-outlined />
                   </Tooltip>
@@ -42,7 +42,7 @@
             </template>
 
             <template #content>
-              <div class="content" v-html="getHtml(item.commentContent)" style="font-size: 15px">
+              <div class="content" v-html="getHtml(lineFeed(item.commentContent))" style="font-size: 15px">
               </div>
 
               <div v-if="item.fileList && item.fileList.length > 0">
@@ -105,6 +105,7 @@
       HistoryFileList,
     },
     props: {
+      tableId: propTypes.string.def(''),
       tableName: propTypes.string.def(''),
       dataId: propTypes.string.def(''),
       datetime:  propTypes.number.def(1),
@@ -281,6 +282,11 @@
           }
         }
       }
+      // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-932】评论加上换行
+      const lineFeed = (content) => {
+        return content.replace(/\n/g, '<br>');
+      };
+      // update-end--author:liaozhiyang---date:20240618---for:【TV360X-932】评论加上换行
 
       return {
         dataList,
@@ -303,6 +309,7 @@
         bottomCommentRef,
         visibleChange,
         listRef,
+        lineFeed,
       };
     },
   });
@@ -319,6 +326,9 @@
     .ant-comment {
       width: 100%;
     }
+    :deep(.ant-comment-avatar) {
+      cursor: default;
+    }
   }
   .comment-author {
     span {

+ 3 - 2
jeecgboot-vue3/src/components/jeecg/comment/CommentPanel.vue

@@ -2,10 +2,10 @@
   <div class="comment-tabs-warp" v-if="showStatus">
     <a-tabs v-if="show" @change="handleChange" :animated="false">
       <a-tab-pane v-if="showComment" tab="评论" key="comment" class="comment-list-tab">
-        <comment-list :tableName="tableName" :dataId="dataId" :datetime="datetime1" :otherHeight="otherHeight"></comment-list>
+        <comment-list :tableId="tableId" :tableName="tableName" :dataId="dataId" :datetime="datetime1" :otherHeight="otherHeight"></comment-list>
       </a-tab-pane>
       <a-tab-pane v-if="showFiles" tab="文件" key="file">
-        <comment-files :tableName="tableName" :dataId="dataId" :datetime="datetime2"></comment-files>
+        <comment-files :tableId="tableId" :tableName="tableName" :dataId="dataId" :datetime="datetime2"></comment-files>
       </a-tab-pane>
       <a-tab-pane v-if="showDataLog" tab="日志" key="log">
         <data-log-list :tableName="tableName" :dataId="dataId" :datetime="datetime3"></data-log-list>
@@ -33,6 +33,7 @@
       DataLogList,
     },
     props: {
+      tableId: propTypes.string.def(''),
       tableName: propTypes.string.def(''),
       dataId: propTypes.string.def(''),
       // 显示评论

+ 57 - 22
jeecgboot-vue3/src/components/jeecg/comment/MyComment.vue

@@ -1,7 +1,7 @@
 <template>
   <div :class="{'comment-active': commentActive}" class="comment-main" @click="handleClickBlank">
-    <textarea ref="commentRef" v-model="myComment" @keyup.enter="sendComment" @input="handleCommentChange" @blur="handleBlur" class="comment-content" :rows="3" placeholder="请输入你的评论,可以@成员" />
-    <div class="comment-content comment-html-shower" :class="{'no-content':noConent, 'top-div': showHtml, 'bottom-div': showHtml == false }" v-html="commentHtml" @click="handleClickHtmlShower"></div>
+    <textarea ref="commentRef" v-model="myComment" @keyup.enter="sendComment" @input="handleCommentChange" @blur="handleBlur" class="comment-content" :rows="3" placeholder="请输入你的评论,可以@成员"></textarea>
+    <div ref="commentContentRef" class="comment-content comment-html-shower" :class="{'no-content':noConent, 'top-div': showHtml, 'bottom-div': showHtml == false }" v-html="commentHtml" @click="handleClickHtmlShower"></div>
     <div class="comment-buttons" v-if="commentActive">
       <div style="cursor: pointer">
         <Tooltip title="选择@用户">
@@ -90,12 +90,18 @@
     setup(props, { emit }) {
       const uploadVisible = ref(false);
       const uploadRef = ref();
+      const commentContentRef = ref<null | HTMLDivElement>(null);
       //注册model
       const [registerModal, { openModal, closeModal }] = useModal();
       const buttonLoading = ref(false);
       const myComment = ref<string>('');
-      function sendComment() {
-        console.log(myComment.value);
+      function sendComment(e) {
+        // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-932】评论加上换行
+        const keyCode = e.keyCode || e.which;
+        if (keyCode == 13 && e.shiftKey) {
+          return;
+        }
+        // update-end--author:liaozhiyang---date:20240618---for:【TV360X-932】评论加上换行
         let content = myComment.value;
         if (!content && content !== '0') {
           disabledButton.value = true;
@@ -153,15 +159,29 @@
           if (realname && username) {
             let str = `${realname}[${username}]`;
             let temp = myComment.value;
+            // update-begin--author:liaozhiyang---date:20240726---for:【TV360X-929】选择@用户,应该插入到光标位置
             if (!temp) {
-              myComment.value = '@' + str;
+              myComment.value = '@' + str + ' ';
             } else {
-              if (temp.endsWith('@')) {
-                myComment.value = temp + str +' ';
+              const index = commentRef.value?.selectionStart ?? temp.length;
+              let startStr = temp.substring(0, index);
+              const endStr = temp.substring(index);
+              if (startStr.endsWith('@')) {
+                if (startStr.length >= 2) {
+                  const i = startStr.length - 1;
+                  const s_str = startStr.substring(0, i);
+                  const e_str = startStr.substring(i);
+                  const spacing = s_str.endsWith(' ') ? '' : ' ';
+                  startStr = s_str + spacing + e_str;
+                }
+                myComment.value = startStr + str + ' ' + endStr;
               } else {
-                myComment.value = '@' + str + ' ' + temp + ' ';
+                const _symbol = startStr && startStr.endsWith(' ') ? '@' : ' @';
+                myComment.value = startStr + _symbol + str + ' ' + endStr;
               }
             }
+            // update-begin--author:liaozhiyang---date:20240726---for:【TV360X-929】选择@用户,应该插入到光标位置
+
             //update-begin---author:wangshuai---date:2024-01-22---for:【QQYUN-8002】选完人,鼠标应该放到后面并在前面加上空格---
             showHtml.value = false;
             commentRef.value.focus();
@@ -171,18 +191,22 @@
         }
         closeModal();        
       }
-
-      function handleCommentChange() {
-        //console.log(1,e)
-      }
-      watch(
-        () => myComment.value,
-        (val) => {
-          if (val && val.endsWith('@')) {
-            openSelectUser();
-          }
+      // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-927】@只有在输入时弹出用户弹窗,删除时不应该弹出
+      function handleCommentChange(e) {
+        if (e.data === '@') {
+          e.target.blur();
+          openSelectUser();
         }
-      );
+      }
+      // watch(
+      //   () => myComment.value,
+      //   (val) => {
+      //     if (val && val.endsWith('@')) {
+      //       openSelectUser();
+      //     }
+      //   }
+      // );
+      // update-end--author:liaozhiyang---date:20240724---for:【TV360X-927】@只有在输入时弹出用户弹窗,删除时不应该弹出
 
       const emojiButton = ref();
       function onSelectEmoji(emoji) {
@@ -255,6 +279,11 @@
       }
       function handleBlur() {
         showHtml.value = true;
+        // update-begin--author:liaozhiyang---date:20240724---for:解决多行获取焦点和失去焦点时滚动位置不一致
+        setTimeout(() => {
+          commentContentRef.value!.scrollTop = commentRef.value.scrollTop;
+        }, 0);
+        // update-end--author:liaozhiyang---date:20240724---for:解决多行获取焦点和失去焦点时滚动位置不一致
       }
       
       const commentActive = ref(false);
@@ -306,7 +335,8 @@
         commentActive,
         noConent,
         changeActive,
-        selectFirstFile
+        selectFirstFile,
+        commentContentRef,
       };
     },
   };
@@ -342,7 +372,9 @@
     width: 100%;
     border: solid 0px;
     outline: none;
-
+    // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-933】评论框拖动之后底部评论列表被覆盖了部分
+    resize: none;
+    // update-end--author:liaozhiyang---date:20240724---for:【TV360X-933】评论框拖动之后底部评论列表被覆盖了部分
     .emoji-item {
       display: inline-block !important;
       width: 0 !important;
@@ -361,7 +393,10 @@
     position: absolute;
     top: 0;
     left: 0;
-    height: 70px;
+    // update-begin--author:liaozhiyang---date:20240724---for:解决多行获取焦点和失去焦点时滚动位置不一致
+    height: 78px;
+    overflow-y: auto;
+    // update-end--author:liaozhiyang---date:20240724---for:解决多行获取焦点和失去焦点时滚动位置不一致
     &.bottom-div {
       z-index: -99;
     }

+ 1 - 0
jeecgboot-vue3/src/components/jeecg/comment/useComment.ts

@@ -124,6 +124,7 @@ export function useCommentWithFile(props) {
   async function saveComment(obj) {
     const {fromUserId, toUserId, commentId, commentContent} = obj;
     let commentData = {
+      tableId: props.tableId,
       tableName: props.tableName,
       tableDataId: props.dataId,
       fromUserId,

+ 4 - 0
jeecgboot-vue3/src/enums/cacheEnum.ts

@@ -49,6 +49,10 @@ export const APP__THEME__COLOR = '__APP__THEME__COLOR__';
 
 // 
 export const ROLE_AUTH_CONFIG_KEY = 'ROLE__AUTH__CONFIG__KEY__';
+// 部门角色权限
+export const DEPART_ROLE_AUTH_CONFIG_KEY = 'DEPART__ROLE__AUTH__CONFIG__KEY__';
+// 部门管理权限
+export const DEPART_MANGE_AUTH_CONFIG_KEY = 'DEPART__MANGE__AUTH__CONFIG__KEY__';
 
 export enum CacheTypeEnum {
   SESSION,

+ 9 - 0
jeecgboot-vue3/src/hooks/system/useJvxeMethods.ts

@@ -70,6 +70,15 @@ export function useJvxeMethod(requestAddOrEdit, classifyIntoFormData, tableRefs,
             //update-end-author:liusq date:2024-06-12  for: TV360X-478 一对多tab,校验未通过时,tab没有跳转
           }
           //update-end-author:taoyan date:2022-11-22 for: VUEN-2866【代码生成】Tab风格 一对多子表校验不通过时,点击提交表单空白了,流程附加页面也有此问题
+          //update-begin---author:wangshuai---date:2024-06-17---for:【TV360X-1064】非原生提交表单滚动校验没通过的项---
+          if (e?.errorFields) {
+            const firstField = e.errorFields[0];
+            if (firstField) {
+              formRef.value.scrollToField(firstField.name, { behavior: 'smooth', block: 'end' });
+            }
+          }
+          return Promise.reject(e?.errorFields);
+          //update-end---author:wangshuai---date:2024-06-17---for:【TV360X-1064】非原生提交表单滚动校验没通过的项---
         } else {
           console.error(e);
         }

+ 3 - 1
jeecgboot-vue3/src/hooks/web/usePermission.ts

@@ -163,7 +163,9 @@ export function usePermission() {
           return true;
         }
       } else {
-        return true;
+        // update-begin--author:liaozhiyang---date:20240705---for:【TV360X-1604】按钮禁用权限在接口中查不到也禁用
+        return false;
+        // update-end--author:liaozhiyang---date:20240705---for:【TV360X-1604】按钮禁用权限在接口中查不到也禁用
       }
     }
     return false;

+ 4 - 5
jeecgboot-vue3/src/hooks/web/useWebSocket.ts

@@ -17,14 +17,13 @@ export function connectWebSocket(url: string) {
   result = useWebSocket(url, {
     // 自动重连 (遇到错误最多重复连接10次)
     autoReconnect: {
-      retries: 10,
-      delay: 5000,
+      retries : 10,
+      delay : 5000
     },
     // 心跳检测
     heartbeat: {
-      message: 'ping',
-      // 如果服务器压力再改回来55秒
-      interval: 5000,
+      message: "ping",
+      interval: 55000
     },
     protocols: [token],
     // update-begin--author:liaozhiyang---date:20240726---for:[issues/6662] 演示系统socket总断,换一个写法

+ 4 - 1
jeecgboot-vue3/src/store/modules/user.ts

@@ -357,7 +357,10 @@ export const useUserStore = defineStore({
       try {
         const { goHome = true, mode, ...ThirdLoginParams } = params;
         const data = await thirdLogin(ThirdLoginParams, mode);
-        const { token } = data;
+        //update-begin---author:wangshuai---date:2024-07-01---for:【issues/6652】开启租户数据隔离,接入钉钉后登录默认租户为0了---
+        const { token, userInfo } = data;
+        this.setTenant(userInfo?.loginTenantId);
+        //update-end---author:wangshuai---date:2024-07-01---for:【issues/6652】开启租户数据隔离,接入钉钉后登录默认租户为0了---
         // save token
         this.setToken(token);
         return this.afterLoginAction(goHome, data);

+ 38 - 0
jeecgboot-vue3/src/utils/areaData/pcaUtils.ts

@@ -0,0 +1,38 @@
+import {areaList} from '@vant/area-data'
+import {freezeDeep} from "@/utils/common/compUtils";
+
+// 扁平化的省市区数据
+export const pcaa = freezeDeep(usePlatPcaaData())
+
+/**
+ * 获取扁平化的省市区数据
+ */
+function usePlatPcaaData() {
+  const {city_list: city, county_list: county, province_list: province} = areaList;
+  const dataMap = new Map<string, Recordable>()
+  const flatData: Recordable = {'86': province}
+  // 省
+  Object.keys(province).forEach((code) => {
+    flatData[code] = {}
+    dataMap.set(code.slice(0, 2), flatData[code])
+  })
+  // 市区
+  Object.keys(city).forEach((code) => {
+    flatData[code] = {}
+    dataMap.set(code.slice(0, 4), flatData[code])
+    // 填充上一级
+    const getProvince = dataMap.get(code.slice(0, 2))
+    if (getProvince) {
+      getProvince[code] = city[code]
+    }
+  });
+  // 县
+  Object.keys(county).forEach((code) => {
+    // 填充上一级
+    const getCity = dataMap.get(code.slice(0, 4))
+    if (getCity) {
+      getCity[code] = county[code]
+    }
+  });
+  return flatData
+}

+ 29 - 1
jeecgboot-vue3/src/utils/common/compUtils.ts

@@ -5,6 +5,7 @@ import { FormSchema } from '/@/components/Form';
 import { reactive } from "vue";
 import { getTenantId, getToken } from "/@/utils/auth";
 import { useUserStoreWithOut } from "/@/store/modules/user";
+import dayjs from 'dayjs';
 
 import { Modal } from "ant-design-vue";
 import { defHttp } from "@/utils/http/axios";
@@ -416,6 +417,14 @@ export function getUserInfoByExpression(expression) {
   if (!expression) {
     return expression;
   }
+  // 当前日期
+  if (expression === 'sys_date' || expression === 'sysDate') {
+    return dayjs().format('YYYY-MM-DD');
+  }
+  // 当前时间
+  if (expression === 'sys_time' || expression === 'sysTime') {
+    return dayjs().format('HH:mm:ss');
+  }
   const userStore = useUserStoreWithOut();
   let userInfo = userStore.getUserInfo;
   if (userInfo) {
@@ -574,4 +583,23 @@ export function translateTitle(data) {
     });
   }
   return data;
-}
+}
+
+/**
+ *
+ * 深度冻结对象
+ * @param obj Object or Array
+ */
+export function freezeDeep(obj: Recordable | Recordable[]) {
+  if (obj != null) {
+    if (Array.isArray(obj)) {
+      obj.forEach(item => freezeDeep(item))
+    } else if (typeof obj === 'object') {
+      Object.values(obj).forEach(value => {
+        freezeDeep(value)
+      })
+    }
+    Object.freeze(obj)
+  }
+  return obj
+}

+ 4 - 2
jeecgboot-vue3/src/utils/common/vxeUtils.ts

@@ -30,8 +30,10 @@ export async function validateFormModelAndTables(validate, formData, cases, prop
         //update-end---author:wangshuai ---date:20220507  for:[VUEN-912]一对多用户组件(所有风格,单表和树没问题)保存报错--------------
         resolve(formData);
       })
-      .catch(() => {
-        reject({ error: VALIDATE_FAILED, index: 0 });
+      //update-begin---author:wangshuai---date:2024-06-17---for:【TV360X-1064】非原生提交表单滚动校验没通过的项---
+      .catch(({ errorFields }) => {
+        reject({ error: VALIDATE_FAILED, index: 0, errorFields: errorFields });
+      //update-end---author:wangshuai---date:2024-06-17---for:【TV360X-1064】非原生提交表单滚动校验没通过的项---
       });
   });
   Object.assign(dataMap, { formValue: values });

+ 113 - 1
jeecgboot-vue3/src/utils/index.ts

@@ -1,5 +1,6 @@
 import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
 import type { App, Plugin } from 'vue';
+import type { FormSchema } from "@/components/Form";
 
 import { unref } from 'vue';
 import { isObject } from '/@/utils/is';
@@ -60,6 +61,7 @@ export function openWindow(url: string, opt?: { target?: TargetContext | string;
 export function getDynamicProps<T, U>(props: T): Partial<U> {
   const ret: Recordable = {};
 
+  // @ts-ignore
   Object.keys(props).map((key) => {
     ret[key] = unref((props as Recordable)[key]);
   });
@@ -78,10 +80,24 @@ export function getValueType(props, field) {
   let valueType = 'string';
   if (formSchema) {
     let schema = formSchema.filter((item) => item.field === field)[0];
-    valueType = schema.componentProps && schema.componentProps.valueType ? schema.componentProps.valueType : valueType;
+    valueType = schema && schema.componentProps && schema.componentProps.valueType ? schema.componentProps.valueType : valueType;
   }
   return valueType;
 }
+
+/**
+ * 获取表单字段值数据类型
+ * @param schema
+ */
+export function getValueTypeBySchema(schema: FormSchema) {
+  let valueType = 'string';
+  if (schema) {
+    const componentProps = schema.componentProps as Recordable;
+    valueType = componentProps?.valueType ? componentProps?.valueType : valueType;
+  }
+  return valueType;
+}
+
 export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized {
   if (!route) return route;
   const { matched, ...opt } = route;
@@ -110,6 +126,7 @@ export const withInstall = <T>(component: T, alias?: string) => {
   
   const comp = component as any;
   comp.install = (app: App) => {
+    // @ts-ignore
     app.component(comp.name || comp.displayName, component);
     if (alias) {
       app.config.globalProperties[alias] = component;
@@ -438,3 +455,98 @@ export const setPopContainer = (node, selector) => {
     return selector;
   }
 };
+
+/**
+ * 2024-06-14
+ * liaozhiyang
+ * 根据控件显示条件
+ * label、value通用,title、val给权限管理用的
+ */
+export function useConditionFilter() {
+
+  // 通用条件
+  const commonConditionOptions = [
+    {label: '为空', value: 'empty', val: 'EMPTY'},
+    {label: '不为空', value: 'not_empty', val: 'NOT_EMPTY'},
+  ]
+
+  // 数值、日期
+  const numberConditionOptions = [
+    { label: '等于', value: 'eq', val: '=' },
+    { label: '在...中', value: 'in', val: 'IN', title: '包含' },
+    { label: '不等于', value: 'ne', val: '!=' },
+    { label: '大于', value: 'gt', val: '>' },
+    { label: '大于等于', value: 'ge', val: '>=' },
+    { label: '小于', value: 'lt', val: '<' },
+    { label: '小于等于', value: 'le', val: '<=' },
+    ...commonConditionOptions,
+  ];
+
+  // 文本、密码、多行文本、富文本、markdown
+  const inputConditionOptions = [
+    { label: '等于', value: 'eq', val: '=' },
+    { label: '模糊', value: 'like', val: 'LIKE' },
+    { label: '以..开始', value: 'right_like', title: '右模糊', val: 'RIGHT_LIKE' },
+    { label: '以..结尾', value: 'left_like', title: '左模糊', val: 'LEFT_LIKE' },
+    { label: '在...中', value: 'in', val: 'IN', title: '包含' },
+    { label: '不等于', value: 'ne', val: '!=' },
+    ...commonConditionOptions,
+  ];
+
+  // 下拉、单选、多选、开关、用户、部门、关联记录、省市区、popup、popupDict、下拉多选、下拉搜索、分类字典、自定义树
+  const selectConditionOptions = [
+    { label: '等于', value: 'eq', val: '=' },
+    { label: '在...中', value: 'in', val: 'IN', title: '包含' },
+    { label: '不等于', value: 'ne', val: '!=' },
+    ...commonConditionOptions,
+  ];
+
+  const def = [
+    { label: '等于', value: 'eq', val: '=' },
+    { label: '模糊', value: 'like', val: 'LIKE' },
+    { label: '以..开始', value: 'right_like', title: '右模糊', val: 'RIGHT_LIKE' },
+    { label: '以..结尾', value: 'left_like', title: '左模糊', val: 'LEFT_LIKE' },
+    { label: '在...中', value: 'in', val: 'IN', title: '包含' },
+    { label: '不等于', value: 'ne', val: '!=' },
+    { label: '大于', value: 'gt', val: '>' },
+    { label: '大于等于', value: 'ge', val: '>=' },
+    { label: '小于', value: 'lt', val: '<' },
+    { label: '小于等于', value: 'le', val: '<=' },
+    ...commonConditionOptions,
+  ];
+
+  const filterCondition = (data) => {
+    if (data.view == 'text' && data.fieldType == 'number') {
+      data.view = 'number';
+    }
+    switch (data.view) {
+      case 'text':
+      case 'textarea':
+      case 'umeditor':
+      case 'markdown':
+      case 'pca':
+      case 'popup':
+        return inputConditionOptions;
+      case 'list':
+      case 'radio':
+      case 'checkbox':
+      case 'switch':
+      case 'sel_user':
+      case 'sel_depart':
+      case 'link_table':
+      case 'popup_dict':
+      case 'list_multi':
+      case 'sel_search':
+      case 'cat_tree':
+      case 'sel_tree':
+        return selectConditionOptions;
+      case 'date':
+      // number是虚拟的
+      case 'number':
+        return numberConditionOptions;
+      default:
+        return def;
+    }
+  };
+  return { filterCondition };
+}

+ 8 - 7
jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts

@@ -788,34 +788,35 @@ export const schemas: FormSchema[] = [
     componentProps: {
       selectPlaceholder: '可选择系统变量',
       inputPlaceholder: '请输入',
+      selectWidth:'200px',
       options: [
         {
           label: '登录用户账号',
-          value: '${sys_user_code}',
+          value: '#{sys_user_code}',
         },
         {
           label: '登录用户名称',
-          value: '${sys_user_name}',
+          value: '#{sys_user_name}',
         },
         {
           label: '当前日期',
-          value: '${sys_date}',
+          value: '#{sys_date}',
         },
         {
           label: '当前时间',
-          value: '${sys_date}',
+          value: '#{sys_time}',
         },
         {
           label: '登录用户部门',
-          value: '${sys_org_code}',
+          value: '#{sys_org_code}',
         },
         {
           label: '用户拥有部门',
-          value: '${sys_multi_org_code}',
+          value: '#{sys_multi_org_code}',
         },
         {
           label: '登录用户租户',
-          value: '${tenant_id}',
+          value: '#{tenant_id}',
         },
       ],
     },

+ 3 - 1
jeecgboot-vue3/src/views/monitor/mynews/index.vue

@@ -44,7 +44,9 @@
       columns: columns,
       formConfig: {
         schemas: searchFormSchema,
-        fieldMapToTime: [['fieldTime', ['createTime_begin', 'createTime_end'], 'YYYY-MM-DD HH:mm:ss']],
+        //update-begin---author:wangshuai---date:2024-06-11---for:【TV360X-545】我的消息列表不能通过时间范围查询---
+        fieldMapToTime: [['sendTime', ['sendTimeBegin', 'sendTimeEnd'], 'YYYY-MM-DD']],
+        //update-end---author:wangshuai---date:2024-06-11---for:【TV360X-545】我的消息列表不能通过时间范围查询---
       },
     },
   });

+ 11 - 2
jeecgboot-vue3/src/views/monitor/mynews/mynews.data.ts

@@ -64,12 +64,21 @@ export const searchFormSchema: FormSchema[] = [
     field: 'titile',
     label: '标题',
     component: 'Input',
-    colProps: { span: 8 },
+    colProps: { span: 6 },
   },
   {
     field: 'sender',
     label: '发布人',
     component: 'Input',
-    colProps: { span: 8 },
+    colProps: { span: 6 },
+  },
+  {
+    field: 'sendTime',
+    label: '发布时间',
+    component: 'RangeDate',
+    componentProps: {
+      valueType: 'Date',
+    },
+    colProps: { span: 6 },
   },
 ];

+ 8 - 2
jeecgboot-vue3/src/views/monitor/quartz/index.vue

@@ -64,7 +64,7 @@
     },
   });
 
-  const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
+  const [registerTable, { reload }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
 
   /**
    * 操作列定义
@@ -172,6 +172,12 @@
    * 批量删除事件
    */
   async function batchHandleDelete() {
-    await batchDeleteQuartz({ ids: selectedRowKeys.value }, reload);
+    await batchDeleteQuartz({ ids: selectedRowKeys.value }, () => {
+      // -update-begin--author:liaozhiyang---date:20240702---for:【TV360X-1662】菜单管理、定时任务批量删除清空选中
+      reload();
+      selectedRows.value = [];
+      selectedRowKeys.value = [];
+      // -update-end--author:liaozhiyang---date:20240702---for:【TV360X-1662】菜单管理、定时任务批量删除清空选中
+    });
   }
 </script>

+ 84 - 0
jeecgboot-vue3/src/views/monitor/route/components/RouteRecycleBinModal.vue

@@ -0,0 +1,84 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" title="路由回收站" :showOkBtn="false" width="1000px" destroyOnClose>
+    <BasicTable @register="registerTable">
+      <template #status="{ record, text }">
+        <a-tag color="pink" v-if="text == 0">禁用</a-tag>
+        <a-tag color="#87d068" v-if="text == 1">正常</a-tag>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { columns } from '../route.data';
+  import { deleteRouteList, putRecycleBin, deleteRecycleBin } from '../route.api';
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  const checkedKeys = ref<Array<string | number>>([]);
+  const [registerModal] = useModalInner(() => {
+    checkedKeys.value = [];
+  });
+  //注册table数据
+  const [registerTable, { reload }] = useTable({
+    rowKey: 'id',
+    api: deleteRouteList,
+    columns: columns,
+    striped: true,
+    useSearchForm: false,
+    bordered: true,
+    showIndexColumn: false,
+    pagination: false,
+    tableSetting: { fullScreen: true },
+    canResize: false,
+    actionColumn: {
+      width: 150,
+      title: '操作',
+      dataIndex: 'action',
+      slots: { customRender: 'action' },
+      fixed: 'right',
+    },
+  });
+
+  /**
+   * 还原事件
+   */
+  async function handleRevert(record) {
+    await putRecycleBin({ ids: record.id }, reload);
+    emit('success');
+  }
+  /**
+   * 删除事件
+   */
+  async function handleDelete(record) {
+    await deleteRecycleBin({ ids: record.id }, reload);
+  }
+
+  //获取操作栏事件
+  function getTableAction(record) {
+    return [
+      {
+        label: '取回',
+        icon: 'ant-design:redo-outlined',
+        popConfirm: {
+          title: '是否确认取回',
+          confirm: handleRevert.bind(null, record),
+        },
+      },
+      {
+        label: '彻底删除',
+        icon: 'ant-design:scissor-outlined',
+        color: 'error',
+        popConfirm: {
+          title: '是否确认删除',
+          confirm: handleDelete.bind(null, record),
+        },
+      },
+    ];
+  }
+</script>

+ 21 - 1
jeecgboot-vue3/src/views/monitor/route/index.vue

@@ -3,6 +3,7 @@
     <BasicTable @register="registerTable" :indexColumnProps="indexColumnProps">
       <template #tableTitle>
         <a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd" style="margin-right: 5px">新增</a-button>
+        <a-button type="primary" @click="openRecycleModal(true)" preIcon="ant-design:hdd-outlined"> 回收站</a-button>
       </template>
       <template #status="{ record, text }">
         <a-tag color="pink" v-if="text == 0">禁用</a-tag>
@@ -13,22 +14,27 @@
       </template>
     </BasicTable>
     <RouteModal @register="registerDrawer" @success="reload" />
+    <!--回收站弹窗-->
+    <RouteRecycleBinModal @register="registerRecycleModal" @success="reload" />
   </div>
 </template>
 <script lang="ts" name="monitor-route" setup>
   import { ref } from 'vue';
   import { BasicTable, TableAction } from '/@/components/Table';
   import { useModal } from '/@/components/Modal';
-  import { getRouteList, deleteRoute } from './route.api';
+  import { getRouteList, deleteRoute, copyRoute } from './route.api';
   import { columns } from './route.data';
   import RouteModal from './RouteModal.vue';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useDrawer } from '/@/components/Drawer';
   import { useListPage } from '/@/hooks/system/useListPage';
+  import RouteRecycleBinModal from './components/RouteRecycleBinModal.vue';
   const { createMessage } = useMessage();
   const [registerDrawer, { openDrawer }] = useDrawer();
   const checkedKeys = ref<Array<string | number>>([]);
 
+  //回收站model
+  const [registerRecycleModal, { openModal: openRecycleModal }] = useModal();
   // 列表页面公共参数、方法
   const { prefixCls, tableContext } = useListPage({
     designScope: 'router-template',
@@ -59,6 +65,13 @@
         label: '编辑',
         onClick: handleEdit.bind(null, record),
       },
+      {
+        label: '复制',
+        popConfirm: {
+          title: '是否确认复制',
+          confirm: handleCopy.bind(null, record),
+        },
+      },
       {
         label: '删除',
         popConfirm: {
@@ -94,6 +107,13 @@
       isUpdate: true,
     });
   }
+  /**
+   * 复制
+   */
+  async function handleCopy(record) {
+    await copyRoute({ id: record.id }, reload);
+    createMessage.success('复制成功');
+  }
 
   /**
    * 删除事件

+ 39 - 0
jeecgboot-vue3/src/views/monitor/route/route.api.ts

@@ -2,9 +2,14 @@ import { defHttp } from '/@/utils/http/axios';
 
 enum Api {
   list = '/sys/gatewayRoute/list',
+  deleteList = '/sys/gatewayRoute/deleteList',
   save = '/sys/gatewayRoute/add',
   edit = '/sys/gatewayRoute/updateAll',
   delete = '/sys/gatewayRoute/delete',
+
+  copyRoute = '/sys/gatewayRoute/copyRoute',
+  batchPutRecycleBin = '/sys/gatewayRoute/putRecycleBin',
+  batchDeleteRecycleBin = '/sys/gatewayRoute/deleteRecycleBin',
 }
 
 /**
@@ -14,6 +19,13 @@ enum Api {
 export const getRouteList = (params) => {
   return defHttp.get({ url: Api.list, params });
 };
+/**
+ * 查询逻辑删除的路由列表
+ * @param params
+ */
+export const deleteRouteList = (params) => {
+  return defHttp.get({ url: Api.deleteList, params });
+};
 
 /**
  * 保存或者更新路由
@@ -32,3 +44,30 @@ export const deleteRoute = (params, handleSuccess) => {
     handleSuccess();
   });
 };
+
+/**
+ * 回收站还原
+ * @param params
+ */
+export const putRecycleBin = (params, handleSuccess) => {
+  return defHttp.put({ url: Api.batchPutRecycleBin, params }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 回收站删除
+ * @param params
+ */
+export const deleteRecycleBin = (params, handleSuccess) => {
+  return defHttp.delete({ url: `${Api.batchDeleteRecycleBin}?ids=${params.ids}` }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 复制
+ */
+export const copyRoute = (params, handleSuccess) => {
+  return defHttp.get({ url: Api.copyRoute, params }).then(() => {
+    handleSuccess();
+  });
+};

+ 1 - 1
jeecgboot-vue3/src/views/monitor/route/route.data.ts

@@ -21,7 +21,7 @@ export const columns: BasicColumn[] = [
     title: '状态',
     dataIndex: 'status',
     slots: { customRender: 'status' },
-    width: 200,
+    width: 150,
   },
 ];
 

+ 1 - 1
jeecgboot-vue3/src/views/system/appconfig/ThirdAppDingTalkConfigForm.vue

@@ -167,7 +167,7 @@
                 Modal.warning(options)
               } else {
                 createMessage.warning({
-                  content: "同步失败,请检查对接信息录入中是否填写正确,并确认是否已开启钉钉配置!",
+                  content: res.message || "同步失败,请检查对接信息录入中是否填写正确,并确认是否已开启钉钉配置!",
                   duration: 5
                 });
               }

+ 2 - 2
jeecgboot-vue3/src/views/system/category/category.data.ts

@@ -18,13 +18,13 @@ export const searchFormSchema: FormSchema[] = [
   {
     label: '名称',
     field: 'name',
-    component: 'Input',
+    component: 'JInput',
     colProps: { span: 6 },
   },
   {
     label: '编码',
     field: 'code',
-    component: 'Input',
+    component: 'JInput',
     colProps: { span: 6 },
   },
 ];

+ 7 - 1
jeecgboot-vue3/src/views/system/depart/components/DepartLeftTree.vue

@@ -323,7 +323,13 @@
   }
 
   function onExportXls() {
-    handleExportXls('部门信息', Api.exportXlsUrl);
+    //update-begin---author:wangshuai---date:2024-07-05---for:【TV360X-1671】部门管理不支持选中的记录导出---
+    let params = {}
+    if(checkedKeys.value && checkedKeys.value.length > 0) {
+      params['selections'] = checkedKeys.value.join(',')
+    }
+    handleExportXls('部门信息', Api.exportXlsUrl,params);
+    //update-end---author:wangshuai---date:2024-07-05---for:【TV360X-1671】部门管理不支持选中的记录导出---
   }
 
   defineExpose({

+ 122 - 22
jeecgboot-vue3/src/views/system/depart/components/DepartRuleTab.vue

@@ -9,7 +9,7 @@
         :checkedKeys="checkedKeys"
         :selectedKeys="selectedKeys"
         :expandedKeys="expandedKeys"
-        :checkStrictly="checkStrictly"
+        :checkStrictly="true"
         style="height: 500px; overflow: auto"
         @check="onCheck"
         @expand="onExpand"
@@ -28,10 +28,12 @@
         <a-dropdown :trigger="['click']" placement="top">
           <template #overlay>
             <a-menu>
-              <a-menu-item key="3" @click="toggleCheckALL(true)">全部勾选</a-menu-item>
-              <a-menu-item key="4" @click="toggleCheckALL(false)">取消全选</a-menu-item>
-              <a-menu-item key="5" @click="toggleExpandAll(true)">展开所有</a-menu-item>
-              <a-menu-item key="6" @click="toggleExpandAll(false)">收起所有</a-menu-item>
+              <a-menu-item key="3" @click="toggleCheckALL(true)">{{ t('component.tree.selectAll') }}</a-menu-item>
+              <a-menu-item key="4" @click="toggleCheckALL(false)">{{ t('component.tree.unSelectAll') }}</a-menu-item>
+              <a-menu-item key="5" @click="toggleExpandAll(true)">{{ t('component.tree.expandAll') }}</a-menu-item>
+              <a-menu-item key="6" @click="toggleExpandAll(false)">{{ t('component.tree.unExpandAll') }}</a-menu-item>
+              <a-menu-item key="7" @click="toggleRelationAll(false)">{{ t('component.tree.checkStrictly') }}</a-menu-item>
+              <a-menu-item key="8" @click="toggleRelationAll(true)">{{ t('component.tree.checkUnStrictly') }}</a-menu-item>
             </a-menu>
           </template>
           <a-button style="float: left">
@@ -54,6 +56,8 @@
   import { queryRoleTreeList, queryDepartPermission, saveDepartPermission } from '../depart.api';
   import { useDesign } from '/@/hooks/web/useDesign';
   import { translateTitle } from '/@/utils/common/compUtils';
+  import { DEPART_MANGE_AUTH_CONFIG_KEY } from '/@/enums/cacheEnum';
+  import { useI18n } from '/@/hooks/web/useI18n';
 
   const { prefixCls } = useDesign('j-depart-form-content');
   const props = defineProps({
@@ -64,29 +68,47 @@
 
   const basicTree = ref();
   const loading = ref<boolean>(false);
+  //树的全部节点信息
+  const allTreeKeys = ref([]);
   const treeData = ref<any[]>([]);
   const expandedKeys = ref<Array<any>>([]);
   const selectedKeys = ref<Array<any>>([]);
   const checkedKeys = ref<Array<any>>([]);
   const lastCheckedKeys = ref<Array<any>>([]);
-  const checkStrictly = ref(true);
+  const checkStrictly = ref(false);
+  const { t } = useI18n();
 
   // 注册数据规则授权弹窗抽屉
   const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer();
 
   // onCreated
-  loadData();
+  loadData({
+    success: (ids) => {
+      // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+      const localData = localStorage.getItem(DEPART_MANGE_AUTH_CONFIG_KEY);
+      if (localData) {
+        const obj = JSON.parse(localData);
+        obj.level && toggleRelationAll(obj.level == 'relation' ? false : true);
+        obj.expand && toggleExpandAll(obj.expand == 'openAll' ? true :false);
+      } else {
+        // expandedKeys.value = ids;
+      }
+      // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+    }
+  });
   watch(departId, () => loadDepartPermission(), { immediate: true });
 
-  async function loadData() {
+  async function loadData(options: any = {}) {
     try {
       loading.value = true;
-      let { treeList } = await queryRoleTreeList();
+      let { treeList, ids } = await queryRoleTreeList();
       //update-begin---author:wangshuai---date:2024-04-08---for:【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
       treeData.value = translateTitle(treeList);
       //update-end---author:wangshuai---date:2024-04-08---for:【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
-      await nextTick();
-      toggleExpandAll(true);
+      // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+      allTreeKeys.value = ids;
+      options.success?.(ids);
+      // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
     } finally {
       loading.value = false;
     }
@@ -120,13 +142,58 @@
     }
   }
 
-  // tree勾选复选框事件
-  function onCheck(event) {
-    if (!Array.isArray(event)) {
-      checkedKeys.value = event.checked;
+  /**
+   * 点击选中
+   * 2024-07-04
+   * liaozhiyang
+   */
+  function onCheck(o, e) {
+    // checkStrictly: true=>层级独立,false=>层级关联.
+    if (checkStrictly.value) {
+      checkedKeys.value = o.checked ? o.checked : o;
     } else {
-      checkedKeys.value = event;
+      const keys = getNodeAllKey(e.node, 'children', 'key');
+      if (e.checked) {
+        // 反复操作下可能会有重复的keys,得用new Set去重下
+        checkedKeys.value = [...new Set([...checkedKeys.value, ...keys])];
+      } else {
+        const result = removeMatchingItems(checkedKeys.value, keys);
+        checkedKeys.value = result;
+      }
+    }
+  }
+  /**
+   * 2024-07-04
+   * liaozhiyang
+   * 删除相匹配数组的项
+   */
+  function removeMatchingItems(arr1, arr2) {
+    // 使用哈希表记录 arr2 中的元素
+    const hashTable = {};
+    for (const item of arr2) {
+      hashTable[item] = true;
     }
+    // 使用 filter 方法遍历第一个数组,过滤出不在哈希表中存在的项
+    return arr1.filter((item) => !hashTable[item]);
+  }
+  /**
+   * 2024-07-04
+   * liaozhiyang
+   * 获取当前节点及以下所有子孙级的key
+   */
+  function getNodeAllKey(node: any, children: any, key: string) {
+    const result: any = [];
+    result.push(node[key]);
+    const recursion = (data) => {
+      data.forEach((item: any) => {
+        result.push(item[key]);
+        if (item[children]?.length) {
+          recursion(item[children]);
+        }
+      });
+    };
+    node[children]?.length && recursion(node[children]);
+    return result;
   }
 
   // tree展开事件
@@ -152,17 +219,50 @@
 
   // 切换展开收起
   async function toggleExpandAll(flag) {
-    basicTree.value.expandAll(flag);
-    await nextTick();
-    expandedKeys.value = basicTree.value.getExpandedKeys();
+    // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+    if (flag) {
+      expandedKeys.value = allTreeKeys.value;
+      saveLocalOperation('expand', 'openAll');
+    } else {
+      expandedKeys.value = [];
+      saveLocalOperation('expand', 'closeAll');
+    }
+    // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
   }
 
   // 切换全选
   async function toggleCheckALL(flag) {
-    basicTree.value.checkAll(flag);
-    await nextTick();
-    checkedKeys.value = basicTree.value.getCheckedKeys();
+    // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+    if (flag) {
+      checkedKeys.value = allTreeKeys.value;
+    } else {
+      checkedKeys.value = [];
+    }
+    // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
   }
+
+  // 切换层级关联(独立)
+  const toggleRelationAll = (flag) => {
+    // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+    checkStrictly.value = flag;
+    if (flag) {
+      saveLocalOperation('level', 'standAlone');
+    } else {
+      saveLocalOperation('level', 'relation');
+    }
+    // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
+  };
+  /**
+   * 2024-07-04
+   * liaozhiyang
+   * 缓存
+   * */
+  const saveLocalOperation = (key, value) => {
+    const localData = localStorage.getItem(DEPART_MANGE_AUTH_CONFIG_KEY);
+    const obj = localData ? JSON.parse(localData) : {};
+    obj[key] = value;
+    localStorage.setItem(DEPART_MANGE_AUTH_CONFIG_KEY, JSON.stringify(obj))
+  };
 </script>
 
 <style lang="less" scoped>

+ 148 - 12
jeecgboot-vue3/src/views/system/departUser/components/DepartRoleAuthDrawer.vue

@@ -1,6 +1,5 @@
 <template>
   <BasicDrawer
-    title="部门角色权限配置"
     :width="650"
     :loading="loading"
     showFooter
@@ -9,18 +8,35 @@
     @close="onClose"
     @register="registerDrawer"
   >
+    <template #title>
+      部门角色权限配置
+      <a-dropdown>
+        <Icon icon="ant-design:more-outlined" class="more-icon" />
+        <template #overlay>
+          <a-menu @click="treeMenuClick">
+            <a-menu-item key="checkAll">{{ t('component.tree.selectAll') }}</a-menu-item>
+            <a-menu-item key="cancelCheck">{{ t('component.tree.unSelectAll') }}</a-menu-item>
+            <div class="line"></div>
+            <a-menu-item key="openAll">{{ t('component.tree.expandAll') }}</a-menu-item>
+            <a-menu-item key="closeAll">{{ t('component.tree.unExpandAll') }}</a-menu-item>
+            <div class="line"></div>
+            <a-menu-item key="relation">{{ t('component.tree.checkStrictly') }}</a-menu-item>
+            <a-menu-item key="standAlone">{{ t('component.tree.checkUnStrictly') }}</a-menu-item>
+          </a-menu>
+        </template>
+      </a-dropdown>
+    </template>
     <div>
       <a-spin :spinning="loading">
         <template v-if="treeData.length > 0">
           <BasicTree
             title="所拥有的部门权限"
-            toolbar
             checkable
             :treeData="treeData"
             :checkedKeys="checkedKeys"
             :selectedKeys="selectedKeys"
             :expandedKeys="expandedKeys"
-            :checkStrictly="checkStrictly"
+            :checkStrictly="true"
             :clickRowToExpand="false"
             @check="onCheck"
             @expand="onExpand"
@@ -49,10 +65,11 @@
   import { BasicTree } from '/@/components/Tree/index';
   import { BasicDrawer, useDrawer, useDrawerInner } from '/@/components/Drawer';
   import { useMessage } from '/@/hooks/web/useMessage';
-
+  import { useI18n } from '/@/hooks/web/useI18n';
   import DepartRoleDataRuleDrawer from './DepartRoleDataRuleDrawer.vue';
   import { queryTreeListForDeptRole, queryDeptRolePermission, saveDeptRolePermission } from '../depart.user.api';
   import { translateTitle } from "@/utils/common/compUtils";
+  import { DEPART_ROLE_AUTH_CONFIG_KEY } from '/@/enums/cacheEnum';
 
   defineEmits(['register']);
   const { createMessage } = useMessage();
@@ -65,25 +82,42 @@
   const expandedKeys = ref<Array<any>>([]);
   const selectedKeys = ref<Array<any>>([]);
   const allTreeKeys = ref<Array<any>>([]);
-  const checkStrictly = ref(true);
+  //父子节点选中状态是否关联 true不关联,false关联
+  const checkStrictly = ref(false);
+  const { t } = useI18n();
 
   // 注册抽屉组件
   const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
     roleId.value = data.record.id;
     departId.value = data.record.departId;
-    loadData();
+    loadData({
+      success: (ids) => {
+        // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1619】同步系统角色改法加上缓存,默认层级关联修正原生层级关联bug
+        const localData = localStorage.getItem(DEPART_ROLE_AUTH_CONFIG_KEY);
+        if (localData) {
+          const obj = JSON.parse(localData);
+          obj.level && treeMenuClick({ key: obj.level });
+          obj.expand && treeMenuClick({ key: obj.expand });
+        } else {
+          // expandedKeys.value = ids;
+        }
+        // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1619】同步系统角色改法加上缓存,默认层级关联修正原生层级关联bug
+      },
+    });
   });
   // 注册数据规则授权弹窗抽屉
   const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer();
 
-  async function loadData() {
+  async function loadData(options: any = {}) {
     try {
       loading.value = true;
       // 用户角色授权功能,查询菜单权限树
       const { ids, treeList } = await queryTreeListForDeptRole({ departId: departId.value });
       if (ids.length > 0) {
         allTreeKeys.value = ids;
-        expandedKeys.value = ids;
+        // update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1619】同步系统角色改法加上缓存,默认层级关联修正原生层级关联bug
+        options.success?.(ids);
+        // update-end--author:liaozhiyang---date:20240704---for:【TV360X-1619】同步系统角色改法加上缓存,默认层级关联修正原生层级关联bug
         //update-begin---author:wangshuai---date:2024-04-08---for:【issues/1169】我的部门功能中的【部门权限】中未翻译 t('') 多语言---
         treeData.value = translateTitle(treeList);
         //update-end---author:wangshuai---date:2024-04-08---for:【issues/1169】我的部门功能中的【部门权限】中未翻译 t('') 多语言---
@@ -107,13 +141,58 @@
     loading.value = false;
   }
 
-  // tree勾选复选框事件
-  function onCheck(event) {
+  /**
+   * 点击选中
+   * 2024-07-04
+   * liaozhiyang
+   */
+  function onCheck(o, e) {
+    // checkStrictly: true=>层级独立,false=>层级关联.
     if (checkStrictly.value) {
-      checkedKeys.value = event.checked;
+      checkedKeys.value = o.checked ? o.checked : o;
     } else {
-      checkedKeys.value = event;
+      const keys = getNodeAllKey(e.node, 'children', 'key');
+      if (e.checked) {
+        // 反复操作下可能会有重复的keys,得用new Set去重下
+        checkedKeys.value = [...new Set([...checkedKeys.value, ...keys])];
+      } else {
+        const result = removeMatchingItems(checkedKeys.value, keys);
+        checkedKeys.value = result;
+      }
+    }
+  }
+  /**
+   * 2024-07-04
+   * liaozhiyang
+   * 删除相匹配数组的项
+   */
+  function removeMatchingItems(arr1, arr2) {
+    // 使用哈希表记录 arr2 中的元素
+    const hashTable = {};
+    for (const item of arr2) {
+      hashTable[item] = true;
     }
+    // 使用 filter 方法遍历第一个数组,过滤出不在哈希表中存在的项
+    return arr1.filter((item) => !hashTable[item]);
+  }
+  /**
+   * 2024-07-04
+   * liaozhiyang
+   * 获取当前节点及以下所有子孙级的key
+   */
+  function getNodeAllKey(node: any, children: any, key: string) {
+    const result: any = [];
+    result.push(node[key]);
+    const recursion = (data) => {
+      data.forEach((item: any) => {
+        result.push(item[key]);
+        if (item[children]?.length) {
+          recursion(item[children]);
+        }
+      });
+    };
+    node[children]?.length && recursion(node[children]);
+    return result;
   }
 
   // tree展开事件
@@ -158,4 +237,61 @@
       }
     }
   }
+
+  /**
+   * 树菜单选择
+   * @param key
+   */
+  function treeMenuClick({ key }) {
+    if (key === 'checkAll') {
+      checkedKeys.value = allTreeKeys.value;
+    } else if (key === 'cancelCheck') {
+      checkedKeys.value = [];
+    } else if (key === 'openAll') {
+      expandedKeys.value = allTreeKeys.value;
+      saveLocalOperation('expand', 'openAll');
+    } else if (key === 'closeAll') {
+      expandedKeys.value = [];
+      saveLocalOperation('expand', 'closeAll');
+    } else if (key === 'relation') {
+      checkStrictly.value = false;
+      saveLocalOperation('level', 'relation');
+    } else {
+      checkStrictly.value = true;
+      saveLocalOperation('level', 'standAlone');
+    }
+  }
+  /**
+   * 2024-07-04
+   * liaozhiyang
+   * */
+  const saveLocalOperation = (key, value) => {
+    const localData = localStorage.getItem(DEPART_ROLE_AUTH_CONFIG_KEY);
+    const obj = localData ? JSON.parse(localData) : {};
+    obj[key] = value;
+    localStorage.setItem(DEPART_ROLE_AUTH_CONFIG_KEY, JSON.stringify(obj));
+  };
 </script>
+<style lang="less" scoped>
+  /** 固定操作按钮 */
+  .jeecg-basic-tree {
+    position: absolute;
+    width: 618px;
+  }
+  .line {
+    height: 1px;
+    width: 100%;
+    border-bottom: 1px solid #f0f0f0;
+  }
+  .more-icon {
+    font-size: 20px !important;
+    color: black;
+    display: inline-flex;
+    float: right;
+    margin-right: 2px;
+    cursor: pointer;
+  }
+  :deep(.jeecg-tree-header) {
+    border-bottom: none;
+  }
+</style>

+ 1 - 1
jeecgboot-vue3/src/views/system/departUser/components/DepartRoleInfoTab.vue

@@ -3,7 +3,7 @@
   <BasicTable @register="registerTable" :rowSelection="rowSelection">
     <!--插槽:table标题-->
     <template #tableTitle>
-      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="addDepartRole">添加部门角色</a-button>
+      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="addDepartRole" :disabled="!departId">添加部门角色</a-button>
       <template v-if="selectedRowKeys.length > 0">
         <a-divider type="vertical" />
         <a-dropdown>

+ 10 - 3
jeecgboot-vue3/src/views/system/departUser/components/DepartUserInfoTab.vue

@@ -3,8 +3,8 @@
   <BasicTable @register="registerTable" :rowSelection="rowSelection">
     <!--插槽:table标题-->
     <template #tableTitle>
-      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="selectAddUser">添加已有用户</a-button>
-      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="createUser">新建用户</a-button>
+      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="selectAddUser" :disabled="!departId">添加已有用户</a-button>
+      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="createUser" :disabled="!departId">新建用户</a-button>
       <template v-if="selectedRowKeys.length > 0">
         <a-dropdown>
           <template #overlay>
@@ -29,7 +29,7 @@
   </BasicTable>
   <UserDrawer @register="registerDrawer" @success="onUserDrawerSuccess" />
   <DepartRoleUserAuthDrawer @register="registerUserAuthDrawer" />
-  <UserSelectModal rowKey="id" @register="registerSelUserModal" @getSelectResult="onSelectUserOk" />
+  <UserSelectModal ref="userSelectModalRef" rowKey="id" @register="registerSelUserModal" @getSelectResult="onSelectUserOk" />
 </template>
 
 <script lang="ts" setup>
@@ -50,6 +50,7 @@
   const props = defineProps({
     data: { require: true, type: Object },
   });
+  const userSelectModalRef: any = ref(null);
   // 当前选中的部门ID,可能会为空,代表未选择部门
   const departId = computed(() => props.data?.id);
 
@@ -93,6 +94,9 @@
       beforeFetch(params) {
         params.depId = departId.value;
       },
+      // update-begin--author:liaozhiyang---date:20240717---for:【TV360X-1861】没部门时不加载用户信息
+      immediate: !!departId.value,
+      // update-end--author:liaozhiyang---date:20240717---for:【TV360X-1861】没部门时不加载用户信息
     },
   });
 
@@ -155,6 +159,9 @@
 
   // 选择添加已有用户
   function selectAddUser() {
+    // update-begin--author:liaozhiyang---date:20240308---for:【TV360X-1613】再次打开还是上次的选中用户,没置空
+    userSelectModalRef.value.rowSelection.selectedRowKeys = [];
+    // update-end--author:liaozhiyang---date:20240308---for:【TV360X-1613】再次打开还是上次的选中用户,没置空
     selUserModal.openModal();
   }
 

+ 57 - 6
jeecgboot-vue3/src/views/system/dict/components/DictRecycleBinModal.vue

@@ -1,6 +1,27 @@
 <template>
   <BasicModal v-bind="$attrs" @register="registerModal" title="字典回收站" :showOkBtn="false" width="1000px" destroyOnClose>
-    <BasicTable @register="registerTable">
+    <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <!--插槽:table标题-->
+      <template #tableTitle>
+        <a-dropdown v-if="checkedKeys.length > 0">
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="batchHandleDelete">
+                <Icon icon="ant-design:delete-outlined"></Icon>
+                批量删除
+              </a-menu-item>
+              <a-menu-item key="2" @click="batchHandleRevert">
+                <Icon icon="ant-design:redo-outlined"></Icon>
+                批量取回
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <a-button
+            >批量操作
+            <Icon icon="ant-design:down-outlined"></Icon>
+          </a-button>
+        </a-dropdown>
+      </template>
       <!--操作栏-->
       <template #action="{ record }">
         <TableAction :actions="getTableAction(record)" />
@@ -13,13 +34,16 @@
   import { BasicModal, useModalInner } from '/src/components/Modal';
   import { BasicTable, useTable, TableAction } from '/src/components/Table';
   import { recycleBincolumns } from '../dict.data';
-  import { getRecycleBinList, putRecycleBin, deleteRecycleBin } from '../dict.api';
+  import { getRecycleBinList, putRecycleBin, deleteRecycleBin, batchPutRecycleBin, batchDeleteRecycleBin } from '../dict.api';
   // 声明Emits
   const emit = defineEmits(['success', 'register']);
   const checkedKeys = ref<Array<string | number>>([]);
-  const [registerModal, { setModalProps, closeModal }] = useModalInner();
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(() => {
+    checkedKeys.value = [];
+  });
   //注册table数据
   const [registerTable, { reload }] = useTable({
+    rowKey: 'id',
     api: getRecycleBinList,
     columns: recycleBincolumns,
     striped: true,
@@ -39,7 +63,23 @@
       fixed: undefined,
     },
   });
-
+  // update-begin--author:liaozhiyang---date:20240709---for:【TV360X-1663】数据字典回收增加批量功能
+  /**
+   * 选择列配置
+   */
+  const rowSelection = {
+    type: 'checkbox',
+    columnWidth: 50,
+    selectedRowKeys: checkedKeys,
+    onChange: onSelectChange,
+  };
+  /**
+   * 选择事件
+   */
+  function onSelectChange(selectedRowKeys: (string | number)[]) {
+    checkedKeys.value = selectedRowKeys;
+  }
+  // update-end--author:liaozhiyang---date:20240709---for:【TV360X-1663】数据字典回收增加批量功能
   /**
    * 还原事件
    */
@@ -57,13 +97,24 @@
    * 批量还原事件
    */
   function batchHandleRevert() {
-    handleRevert({ id: toRaw(checkedKeys.value).join(',') });
+    batchPutRecycleBin({ ids: toRaw(checkedKeys.value).join(',') }, () => {
+      // update-begin--author:liaozhiyang---date:20240709---for:【TV360X-1663】数据字典回收增加批量功能
+      reload();
+      checkedKeys.value = [];
+      emit('success');
+      // update-end--author:liaozhiyang---date:20240709---for:【TV360X-1663】数据字典回收增加批量功能
+    });
   }
   /**
    * 批量删除事件
    */
   function batchHandleDelete() {
-    handleDelete({ id: toRaw(checkedKeys.value).join(',') });
+    batchDeleteRecycleBin({ ids: toRaw(checkedKeys.value).join(',') }, () => {
+      // update-begin--author:liaozhiyang---date:20240709---for:【TV360X-1663】数据字典回收增加批量功能
+      checkedKeys.value = [];
+      reload();
+      // update-end--author:liaozhiyang---date:20240709---for:【TV360X-1663】数据字典回收增加批量功能
+    });
   }
   //获取操作栏事件
   function getTableAction(record) {

+ 21 - 0
jeecgboot-vue3/src/views/system/dict/dict.api.ts

@@ -11,6 +11,8 @@ enum Api {
   exportXls = '/sys/dict/exportXls',
   recycleBinList = '/sys/dict/deleteList',
   putRecycleBin = '/sys/dict/back',
+  batchPutRecycleBin = '/sys/dict/putRecycleBin',
+  batchDeleteRecycleBin = '/sys/dict/deleteRecycleBin',
   deleteRecycleBin = '/sys/dict/deletePhysic',
   itemList = '/sys/dictItem/list',
   deleteItem = '/sys/dictItem/delete',
@@ -78,6 +80,16 @@ export const duplicateCheck = (params) => defHttp.get({ url: Api.duplicateCheck,
  * @param params
  */
 export const getRecycleBinList = (params) => defHttp.get({ url: Api.recycleBinList, params });
+
+/**
+ * 回收站批量还原
+ * @param params
+ */
+export const batchPutRecycleBin = (params, handleSuccess) => {
+  return defHttp.put({ url: Api.batchPutRecycleBin, params}).then(() => {
+    handleSuccess();
+  });
+};
 /**
  * 回收站还原
  * @param params
@@ -87,6 +99,15 @@ export const putRecycleBin = (id, handleSuccess) => {
     handleSuccess();
   });
 };
+/**
+ * 回收站批量删除
+ * @param params
+ */
+export const batchDeleteRecycleBin = (params, handleSuccess) => {
+  return defHttp.delete({ url: `${Api.batchDeleteRecycleBin}?ids=${params.ids}`}).then(() => {
+    handleSuccess();
+  });
+};
 /**
  * 回收站删除
  * @param params

+ 2 - 2
jeecgboot-vue3/src/views/system/dict/dict.data.ts

@@ -44,13 +44,13 @@ export const searchFormSchema: FormSchema[] = [
   {
     label: '字典名称',
     field: 'dictName',
-    component: 'Input',
+    component: 'JInput',
     colProps: { span: 6 },
   },
   {
     label: '字典编码',
     field: 'dictCode',
-    component: 'Input',
+    component: 'JInput',
     colProps: { span: 6 },
   },
 ];

+ 8 - 2
jeecgboot-vue3/src/views/system/dict/index.vue

@@ -89,7 +89,7 @@
   });
 
   //注册table数据
-  const [registerTable, { reload, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
+  const [registerTable, { reload, updateTableDataRecord }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
 
   /**
    * 新增事件
@@ -127,7 +127,13 @@
    * 批量删除事件
    */
   async function batchHandleDelete() {
-    await batchDeleteDict({ ids: selectedRowKeys.value }, reload);
+    await batchDeleteDict({ ids: selectedRowKeys.value }, () => {
+      // update-begin--author:liaozhiyang---date:20240701---for:【TV360X-1665】数据字典批量删除后选中也清空
+      reload();
+      selectedRowKeys.value = [];
+      selectedRows.value = [];
+      // update-end--author:liaozhiyang---date:20240701---for:【TV360X-1665】数据字典批量删除后选中也清空
+    });
   }
   /**
    * 成功回调

+ 23 - 1
jeecgboot-vue3/src/views/system/menu/DataRuleList.vue

@@ -1,5 +1,5 @@
 <template>
-  <BasicDrawer v-bind="$attrs" @register="registerDrawer" title="数据权限规则" :width="adaptiveWidth">
+  <BasicDrawer v-bind="$attrs" @register="registerDrawer" title="数据权限规则" :width="adaptiveWidth" :rootClassName="prefixCls">
     <BasicTable @register="registerTable">
       <template #tableTitle>
         <a-button type="primary" @click="handleCreate"> 新增</a-button>
@@ -21,8 +21,10 @@
   import { dataRuleList, deleteRule } from './menu.api';
   import { ColEx } from '/@/components/Form/src/types';
   import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
+  import { useDesign } from '/@/hooks/web/useDesign';
   const permissionId = ref('');
   const { adaptiveWidth } = useDrawerAdaptiveWidth();
+  const { prefixCls } = useDesign('sys-menu-dataRulelist');
   //权限规则model
   const [registerModal, { openModal }] = useModal();
   const [registerDrawer] = useDrawerInner(async (data) => {
@@ -120,3 +122,23 @@
     ];
   }
 </script>
+<style lang="less">
+  // -update-begin--author:liaozhiyang---date:20240702---for:【TV360X-1660】菜单管理-数据权限的查询和按钮没间隙
+  @prefix-cls: ~'@{namespace}-sys-menu-dataRulelist';
+  .@{prefix-cls} {
+    .jeecg-basic-table {
+      padding: 0;
+    }
+    .btnArea {
+      .ant-btn {
+        &:last-child {
+          margin-right: 0;
+        }
+        &:first-child {
+          margin-left: 8px;
+        }
+      }
+    }
+  }
+  // -update-end--author:liaozhiyang---date:20240702---for:【TV360X-1660】菜单管理-数据权限的查询和按钮没间隙
+</style>

+ 8 - 3
jeecgboot-vue3/src/views/system/menu/index.vue

@@ -78,8 +78,8 @@
       tableSetting: { fullScreen: true },
       formConfig: {
         // update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
-        labelWidth:60,
-        owProps: { gutter: 24 },
+        labelWidth: 74,
+        rowProps: { gutter: 24 },
         // update-end--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
         schemas: searchFormSchema,
         autoAdvancedCol: 4,
@@ -167,7 +167,12 @@
    * 批量删除事件
    */
   async function batchHandleDelete() {
-    await batchDeleteMenu({ ids: checkedKeys.value }, reload);
+    await batchDeleteMenu({ ids: checkedKeys.value }, () => {
+      // -update-begin--author:liaozhiyang---date:20240702---for:【TV360X-1662】菜单管理、定时任务批量删除清空选中
+      reload();
+      checkedKeys.value = [];
+      // -update-end--author:liaozhiyang---date:20240702---for:【TV360X-1662】菜单管理、定时任务批量删除清空选中
+    });
   }
   /**
    * 成功回调

+ 39 - 1
jeecgboot-vue3/src/views/system/menu/menu.data.ts

@@ -399,12 +399,50 @@ export const dataRuleFormSchema: FormSchema[] = [
       getPopupContainer: (node) => document.body,
     },
   },
+  // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1864】添加系统变量
   {
     field: 'ruleValue',
+    component: 'JInputSelect',
     label: '规则值',
-    component: 'Input',
     required: true,
+    componentProps: {
+      selectPlaceholder: '可选择系统变量',
+      inputPlaceholder: '请输入',
+      getPopupContainer: () => document.body,
+      selectWidth: '200px',
+      options: [
+        {
+          label: '登录用户账号',
+          value: '#{sys_user_code}',
+        },
+        {
+          label: '登录用户名称',
+          value: '#{sys_user_name}',
+        },
+        {
+          label: '当前日期',
+          value: '#{sys_date}',
+        },
+        {
+          label: '当前时间',
+          value: '#{sys_time}',
+        },
+        {
+          label: '登录用户部门',
+          value: '#{sys_org_code}',
+        },
+        {
+          label: '用户拥有部门',
+          value: '#{sys_multi_org_code}',
+        },
+        {
+          label: '登录用户租户',
+          value: '#{tenant_id}',
+        },
+      ],
+    },
   },
+  // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1864】添加系统变量
   {
     field: 'status',
     label: '状态',

+ 3 - 0
jeecgboot-vue3/src/views/system/notice/DetailModal.vue

@@ -20,5 +20,8 @@
     width: 100%;
     height: 100%;
     min-height: 500px;
+    // -update-begin--author:liaozhiyang---date:20240702---for:【TV360X-1685】通知公告查看出现两个滚动条
+    display: block;
+    // -update-end--author:liaozhiyang---date:20240702---for:【TV360X-1685】通知公告查看出现两个滚动条
   }
 </style>

+ 5 - 2
jeecgboot-vue3/src/views/system/notice/NoticeModal.vue

@@ -41,13 +41,16 @@
       let values = await validate();
       setModalProps({ confirmLoading: true });
       //提交表单
-      //update-begin-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined --- 
+      //update-begin-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined ---
       if(values.msgType==='ALL'){
         values.userIds = '';
       }else{
         values.userIds += ',';
       }
-      //update-end-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined --- 
+      //update-end-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined ---
+      if (isUpdate.value) {
+        values.sendStatus = '0';
+      }
       await saveOrUpdate(values, isUpdate.value);
       //关闭弹窗
       closeModal();

+ 1 - 1
jeecgboot-vue3/src/views/system/notice/index.vue

@@ -129,7 +129,7 @@
       {
         label: '编辑',
         onClick: handleEdit.bind(null, record),
-        ifShow: record.sendStatus == 0,
+        ifShow: record.sendStatus == 0 || record.sendStatus == '2',
       },
     ];
   }

+ 19 - 1
jeecgboot-vue3/src/views/system/notice/notice.data.ts

@@ -95,6 +95,22 @@ export const formSchema: FormSchema[] = [
     componentProps: {
       placeholder: '请输入标题',
     },
+    // update-begin--author:liaozhiyang---date:20240701---for:【TV360X-1632】标题过长保存报错,长度校验
+    dynamicRules() {
+      return [
+        {
+          validator: (_, value) => {
+            return new Promise<void>((resolve, reject) => {
+              if (value.length > 100) {
+                reject('最长100个字符');
+              }
+              resolve();
+            });
+          },
+        },
+      ];
+    },
+    // update-end--author:liaozhiyang---date:20240701---for:【TV360X-1632】标题过长保存报错,长度校验
   },
   {
     field: 'msgAbstract',
@@ -132,7 +148,9 @@ export const formSchema: FormSchema[] = [
     required: true,
     componentProps: {
       rowKey: 'id',
-      labelKey: 'username',
+      // update-begin--author:liaozhiyang---date:20240701---for:【TV360X-1627】通知公告用户选择组件没翻译
+      labelKey: 'realname',
+      // update-end--author:liaozhiyang---date:20240701---for:【TV360X-1627】通知公告用户选择组件没翻译
     },
     ifShow: ({ values }) => values.msgType == 'USER',
   },

+ 6 - 1
jeecgboot-vue3/src/views/system/role/components/RoleUserTable.vue

@@ -144,7 +144,12 @@
    * 批量删除事件
    */
   async function batchHandleDelete() {
-    await batchDeleteUserRole({ userIds: checkedKeys.value.join(','), roleId: roleId.value }, reload);
+    await batchDeleteUserRole({ userIds: checkedKeys.value.join(','), roleId: roleId.value }, () => {
+      // update-begin--author:liaozhiyang---date:20240701---for:【TV360X-1655】批量取消关联之后清空选中记录
+      reload();
+      checkedKeys.value = [];
+      // update-end--author:liaozhiyang---date:20240701---for:【TV360X-1655】批量取消关联之后清空选中记录
+    });
   }
 
   /**

+ 0 - 0
jeecgboot-vue3/src/views/system/role/components/UseSelectModal.vue


Some files were not shown because too many files changed in this diff